diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage new file mode 100644 index 00000000000000..d9ec1861c9979e --- /dev/null +++ b/.ci/Jenkinsfile_coverage @@ -0,0 +1,112 @@ +#!/bin/groovy + +library 'kibana-pipeline-library' +kibanaLibrary.load() // load from the Jenkins instance + +stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a little bit + timeout(time: 180, unit: 'MINUTES') { + timestamps { + ansiColor('xterm') { + catchError { + withEnv([ + 'CODE_COVERAGE=1', // Needed for multiple ci scripts, such as remote.ts, test/scripts/*.sh, schema.js, etc. + ]) { + parallel([ + 'kibana-intake-agent': { + withEnv([ + 'NODE_ENV=test' // Needed for jest tests only + ]) { + kibanaPipeline.legacyJobRunner('kibana-intake')() + } + }, + 'x-pack-intake-agent': { + withEnv([ + 'NODE_ENV=test' // Needed for jest tests only + ]) { + kibanaPipeline.legacyJobRunner('x-pack-intake')() + } + }, + 'kibana-oss-agent': kibanaPipeline.withWorkers('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ + 'oss-ciGroup1': kibanaPipeline.getOssCiGroupWorker(1), + 'oss-ciGroup2': kibanaPipeline.getOssCiGroupWorker(2), + 'oss-ciGroup3': kibanaPipeline.getOssCiGroupWorker(3), + 'oss-ciGroup4': kibanaPipeline.getOssCiGroupWorker(4), + 'oss-ciGroup5': kibanaPipeline.getOssCiGroupWorker(5), + 'oss-ciGroup6': kibanaPipeline.getOssCiGroupWorker(6), + 'oss-ciGroup7': kibanaPipeline.getOssCiGroupWorker(7), + 'oss-ciGroup8': kibanaPipeline.getOssCiGroupWorker(8), + 'oss-ciGroup9': kibanaPipeline.getOssCiGroupWorker(9), + 'oss-ciGroup10': kibanaPipeline.getOssCiGroupWorker(10), + 'oss-ciGroup11': kibanaPipeline.getOssCiGroupWorker(11), + 'oss-ciGroup12': kibanaPipeline.getOssCiGroupWorker(12), + ]), + 'kibana-xpack-agent-1': kibanaPipeline.withWorkers('kibana-xpack-tests-1', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), + 'xpack-ciGroup2': kibanaPipeline.getXpackCiGroupWorker(2), + ]), + 'kibana-xpack-agent-2': kibanaPipeline.withWorkers('kibana-xpack-tests-2', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup3': kibanaPipeline.getXpackCiGroupWorker(3), + 'xpack-ciGroup4': kibanaPipeline.getXpackCiGroupWorker(4), + ]), + + 'kibana-xpack-agent-3': kibanaPipeline.withWorkers('kibana-xpack-tests-3', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup5': kibanaPipeline.getXpackCiGroupWorker(5), + 'xpack-ciGroup6': kibanaPipeline.getXpackCiGroupWorker(6), + 'xpack-ciGroup7': kibanaPipeline.getXpackCiGroupWorker(7), + 'xpack-ciGroup8': kibanaPipeline.getXpackCiGroupWorker(8), + 'xpack-ciGroup9': kibanaPipeline.getXpackCiGroupWorker(9), + 'xpack-ciGroup10': kibanaPipeline.getXpackCiGroupWorker(10), + ]), + ]) + kibanaPipeline.jobRunner('tests-l', false) { + kibanaPipeline.downloadCoverageArtifacts() + kibanaPipeline.bash( + ''' + # bootstrap from x-pack folder + source src/dev/ci_setup/setup_env.sh + cd x-pack + yarn kbn bootstrap --prefer-offline + cd .. + # extract archives + mkdir -p /tmp/extracted_coverage + echo extracting intakes + tar -xzf /tmp/downloaded_coverage/coverage/kibana-intake/kibana-coverage.tar.gz -C /tmp/extracted_coverage + tar -xzf /tmp/downloaded_coverage/coverage/x-pack-intake/kibana-coverage.tar.gz -C /tmp/extracted_coverage + echo extracting kibana-oss-tests + tar -xzf /tmp/downloaded_coverage/coverage/kibana-oss-tests/kibana-coverage.tar.gz -C /tmp/extracted_coverage + echo extracting kibana-xpack-tests + for i in {1..3}; do + tar -xzf /tmp/downloaded_coverage/coverage/kibana-xpack-tests-${i}/kibana-coverage.tar.gz -C /tmp/extracted_coverage + done + # replace path in json files to have valid html report + pwd=$(pwd) + du -sh /tmp/extracted_coverage/target/kibana-coverage/ + echo replacing path in json files + for i in {1..9}; do + sed -i "s|/dev/shm/workspace/kibana|$pwd|g" /tmp/extracted_coverage/target/kibana-coverage/functional/${i}*.json & + done + wait + # merge oss & x-pack reports + echo merging coverage reports + yarn nyc report --temp-dir /tmp/extracted_coverage/target/kibana-coverage/jest --report-dir target/kibana-coverage/jest-combined --reporter=html --reporter=json-summary + yarn nyc report --temp-dir /tmp/extracted_coverage/target/kibana-coverage/functional --report-dir target/kibana-coverage/functional-combined --reporter=html --reporter=json-summary + echo copy mocha reports + mkdir -p target/kibana-coverage/mocha-combined + cp -r /tmp/extracted_coverage/target/kibana-coverage/mocha target/kibana-coverage/mocha-combined + ''', + "run `yarn kbn bootstrap && merge coverage`" + ) + sh 'tar -czf kibana-jest-coverage.tar.gz target/kibana-coverage/jest-combined/*' + kibanaPipeline.uploadCoverageArtifacts("coverage/jest-combined", 'kibana-jest-coverage.tar.gz') + sh 'tar -czf kibana-functional-coverage.tar.gz target/kibana-coverage/functional-combined/*' + kibanaPipeline.uploadCoverageArtifacts("coverage/functional-combined", 'kibana-functional-coverage.tar.gz') + sh 'tar -czf kibana-mocha-coverage.tar.gz target/kibana-coverage/mocha-combined/*' + kibanaPipeline.uploadCoverageArtifacts("coverage/mocha-combined", 'kibana-mocha-coverage.tar.gz') + } + } + } + kibanaPipeline.sendMail() + } + } + } +} diff --git a/.eslintrc.js b/.eslintrc.js index 8a9d4da6178e90..c43366abf0c3d8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -113,12 +113,6 @@ module.exports = { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['src/legacy/core_plugins/vis_type_vega/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/legacy/ui/public/vis/**/*.{js,ts,tsx}'], rules: { @@ -247,6 +241,7 @@ module.exports = { '!x-pack/test/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'src/core/(public|server)/**/*', + 'examples/**/*', ], from: [ 'src/core/public/**/*', @@ -283,11 +278,15 @@ module.exports = { 'x-pack/legacy/plugins/**/*', '!x-pack/legacy/plugins/*/server/**/*', '!x-pack/legacy/plugins/*/index.{js,ts,tsx}', + + 'examples/**/*', + '!examples/**/server/**/*', ], from: [ 'src/core/server', 'src/core/server/**/*', '(src|x-pack)/plugins/*/server/**/*', + 'examples/**/server/**/*', ], errorMessage: 'Server modules cannot be imported into client modules or shared modules.', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1137fb99f81a73..a0a22446ba31d1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,7 @@ /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/home/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app +/src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/plugins/home/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app @@ -30,6 +31,7 @@ /src/plugins/visualizations/ @elastic/kibana-app-arch /x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch /src/legacy/core_plugins/data/ @elastic/kibana-app-arch +/src/legacy/core_plugins/elasticsearch/lib/create_proxy.js @elastic/kibana-app-arch /src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch /src/legacy/core_plugins/interpreter/ @elastic/kibana-app-arch /src/legacy/core_plugins/kibana_react/ @elastic/kibana-app-arch @@ -146,6 +148,3 @@ /x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui /x-pack/legacy/plugins/watcher/ @elastic/es-ui - -# Kibana TSVB external contractors -/src/legacy/core_plugins/metrics/ @elastic/kibana-tsvb-external diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06e08c85dafec0..6ae3db559b61ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,6 +190,19 @@ These snapshots are built on a nightly basis which expire after a couple weeks. yarn es snapshot ``` +##### Keeping data between snapshots + +If you want to keep the data inside your Elasticsearch between usages of this command, +you should use the following command, to keep your data folder outside the downloaded snapshot +folder: + +```bash +yarn es snapshot -E path.data=../data +``` + +The same parameter can be used with the source and archive command shown in the following +paragraphs. + #### Source By default, it will reference an [elasticsearch](https://github.com/elastic/elasticsearch) checkout which is a sibling to the Kibana directory named `elasticsearch`. If you wish to use a checkout in another location you can provide that by supplying `--source-path` diff --git a/docs/apm/settings.asciidoc b/docs/apm/settings.asciidoc index 2fc8748f13b096..37122fc9c635d0 100644 --- a/docs/apm/settings.asciidoc +++ b/docs/apm/settings.asciidoc @@ -3,8 +3,16 @@ [[apm-settings-in-kibana]] === APM settings in Kibana -You do not need to configure any settings to use APM. It is enabled by default. -If you'd like to change any of the default values, -copy and paste the relevant settings below into your `kibana.yml` configuration file. +You do not need to configure any settings to use the APM app. It is enabled by default. + +[float] +[[apm-indices-settings]] +==== APM Indices + +include::./../settings/apm-settings.asciidoc[tag=apm-indices-settings] + +[float] +[[general-apm-settings]] +==== General APM settings include::./../settings/apm-settings.asciidoc[tag=general-apm-settings] diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index ec0863b09d653c..22279b69b70feb 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -17,6 +17,7 @@ This section can help with any of the following: There are a number of factors that could be at play here. One important thing to double-check first is your index template. +*Index template* An APM index template must exist for the APM app to work correctly. By default, this index template is created by APM Server on startup. However, this only happens if `setup.template.enabled` is `true` in `apm-server.yml`. @@ -34,14 +35,21 @@ GET /_template/apm-{version} -------------------------------------------------- // CONSOLE +*Using Logstash, Kafka, etc.* If you're not outputting data directly from APM Server to Elasticsearch (perhaps you're using Logstash or Kafka), then the index template will not be set up automatically. Instead, you'll need to -{apm-server-ref}/_manually_loading_template_configuration.html#load-template-manually-alternate[load the template manually]. +{apm-server-ref}/_manually_loading_template_configuration.html[load the template manually]. -Finally, this problem can also occur if you've changed the index name that you write APM data to. -The default index pattern can be found {apm-server-ref}/elasticsearch-output.html#index-option-es[here]. -If you change this setting, you must also configure the `setup.template.name` and `setup.template.pattern` options. +*Using a custom index names* +This problem can also occur if you've customized the index name that you write APM data to. +The default index name that APM writes events to can be found +{apm-server-ref}/elasticsearch-output.html#index-option-es[here]. +If you change the default, you must also configure the `setup.template.name` and `setup.template.pattern` options. See {apm-server-ref}/configuration-template.html[Load the Elasticsearch index template]. +If the Elasticsearch index template has already been successfully loaded to the index, +you can customize the indices that the APM app uses to display data. +Navigate to *APM* > *Settings* > *Indices*, and change all `apm_oss.*Pattern` values to +include the new index pattern. For example: `customIndexName-*`. ==== Unknown route diff --git a/docs/developer/plugin/development-uiexports.asciidoc b/docs/developer/plugin/development-uiexports.asciidoc index 6368446f7fb437..18d326cbfb9c08 100644 --- a/docs/developer/plugin/development-uiexports.asciidoc +++ b/docs/developer/plugin/development-uiexports.asciidoc @@ -9,8 +9,8 @@ An aggregate list of available UiExport types: | hacks | Any module that should be included in every application | visTypes | Modules that register providers with the `ui/registry/vis_types` registry. | inspectorViews | Modules that register custom inspector views via the `viewRegistry` in `ui/inspector`. -| chromeNavControls | Modules that register providers with the `ui/registry/chrome_nav_controls` registry. -| navbarExtensions | Modules that register providers with the `ui/registry/navbar_extensions` registry. -| docViews | Modules that register providers with the `ui/registry/doc_views` registry. +| chromeNavControls | Modules that register providers with the `ui/registry/chrome_header_nav_controls` registry. +| navbarExtensions | Modules that register providers with the setup contract of the `navigation` plugin. +| docViews | Modules that register providers with the setup contract method `addDocView` of the `discover` plugin. | app | Adds an application to the system. This uiExport type is defined as an object of metadata rather than just a module id. |======================================================================= diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md index 1ce18834f53196..a4fa3f17d0d94f 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md @@ -9,5 +9,5 @@ Search for objects Signature: ```typescript -find: (options: Pick) => Promise>; +find: (options: Pick) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index 3b916db9726738..88485aa71f7c54 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -24,7 +24,7 @@ The constructor for this class is marked as internal. Third-party code should no | [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | | [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | | [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md b/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md index ed7d028a1ec8a2..bb1f481c9ef4fb 100644 --- a/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md +++ b/docs/development/core/server/kibana-plugin-server.clusterclient.asscoped.md @@ -9,14 +9,14 @@ Creates an instance of [IScopedClusterClient](./kibana-plugin-server.iscopedclus Signature: ```typescript -asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; +asScoped(request?: ScopeableRequest): IScopedClusterClient; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| request | KibanaRequest | LegacyRequest | FakeRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | +| request | ScopeableRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.clusterclient.md b/docs/development/core/server/kibana-plugin-server.clusterclient.md index 5fdda7ef3e499a..d547b846e65b79 100644 --- a/docs/development/core/server/kibana-plugin-server.clusterclient.md +++ b/docs/development/core/server/kibana-plugin-server.clusterclient.md @@ -4,7 +4,7 @@ ## ClusterClient class -Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). +Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). See [ClusterClient](./kibana-plugin-server.clusterclient.md). diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient.md new file mode 100644 index 00000000000000..415423f555266d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [adminClient](./kibana-plugin-server.elasticsearchservicesetup.adminclient.md) + +## ElasticsearchServiceSetup.adminClient property + +A client for the `admin` cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). + +Signature: + +```typescript +readonly adminClient: IClusterClient; +``` + +## Example + + +```js +const client = core.elasticsearch.adminClient; + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md deleted file mode 100644 index b5bfc68d3ca0c4..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [adminClient$](./kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) - -## ElasticsearchServiceSetup.adminClient$ property - -Observable of clients for the `admin` cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). - - -```js -const client = await elasticsearch.adminClient$.pipe(take(1)).toPromise(); - -``` - -Signature: - -```typescript -readonly adminClient$: Observable; -``` diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md index 3d26f2d4cec889..797f402cc25807 100644 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md @@ -9,7 +9,7 @@ Create application specific Elasticsearch cluster API client with customized con Signature: ```typescript -readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; +readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient.md new file mode 100644 index 00000000000000..e9845dce6915d0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [dataClient](./kibana-plugin-server.elasticsearchservicesetup.dataclient.md) + +## ElasticsearchServiceSetup.dataClient property + +A client for the `data` cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). + +Signature: + +```typescript +readonly dataClient: IClusterClient; +``` + +## Example + + +```js +const client = core.elasticsearch.dataClient; + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md deleted file mode 100644 index 9411f2f6b86946..00000000000000 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) > [dataClient$](./kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) - -## ElasticsearchServiceSetup.dataClient$ property - -Observable of clients for the `data` cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). - - -```js -const client = await elasticsearch.dataClient$.pipe(take(1)).toPromise(); - -``` - -Signature: - -```typescript -readonly dataClient$: Observable; -``` diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md index e3d151cdc0d8bb..2de3f6e6d1bbca 100644 --- a/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.md @@ -15,17 +15,7 @@ export interface ElasticsearchServiceSetup | Property | Type | Description | | --- | --- | --- | -| [adminClient$](./kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Observable<IClusterClient> | Observable of clients for the admin cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). -```js -const client = await elasticsearch.adminClient$.pipe(take(1)).toPromise(); - -``` - | -| [createClient](./kibana-plugin-server.elasticsearchservicesetup.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => IClusterClient | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | -| [dataClient$](./kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Observable<IClusterClient> | Observable of clients for the data cluster. Observable emits when Elasticsearch config changes on the Kibana server. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). -```js -const client = await elasticsearch.dataClient$.pipe(take(1)).toPromise(); - -``` - | +| [adminClient](./kibana-plugin-server.elasticsearchservicesetup.adminclient.md) | IClusterClient | A client for the admin cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | +| [createClient](./kibana-plugin-server.elasticsearchservicesetup.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | +| [dataClient](./kibana-plugin-server.elasticsearchservicesetup.dataclient.md) | IClusterClient | A client for the data cluster. All Elasticsearch config value changes are processed under the hood. See [IClusterClient](./kibana-plugin-server.iclusterclient.md). | diff --git a/docs/development/core/server/kibana-plugin-server.iclusterclient.md b/docs/development/core/server/kibana-plugin-server.iclusterclient.md index 834afa6db51570..e7435a9d91a74d 100644 --- a/docs/development/core/server/kibana-plugin-server.iclusterclient.md +++ b/docs/development/core/server/kibana-plugin-server.iclusterclient.md @@ -4,12 +4,12 @@ ## IClusterClient type -Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). +Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). See [ClusterClient](./kibana-plugin-server.clusterclient.md). Signature: ```typescript -export declare type IClusterClient = Pick; +export declare type IClusterClient = Pick; ``` diff --git a/docs/development/core/server/kibana-plugin-server.icustomclusterclient.md b/docs/development/core/server/kibana-plugin-server.icustomclusterclient.md new file mode 100644 index 00000000000000..bc9ea71bc23893 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icustomclusterclient.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) + +## ICustomClusterClient type + +Represents an Elasticsearch cluster API client created by a plugin.. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). + +See [ClusterClient](./kibana-plugin-server.clusterclient.md). + +Signature: + +```typescript +export declare type ICustomClusterClient = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 5e7f84c55244d4..3f01048846fe1a 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -17,7 +17,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Class | Description | | --- | --- | | [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | -| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | @@ -175,8 +175,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [Headers](./kibana-plugin-server.headers.md) | Http request headers to read. | | [HttpResponsePayload](./kibana-plugin-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | -| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin.. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | | [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | @@ -213,6 +214,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-server.kibanarequest.md). | | [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) | | | [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.scopeablerequest.md b/docs/development/core/server/kibana-plugin-server.scopeablerequest.md new file mode 100644 index 00000000000000..5a9443376996d1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.scopeablerequest.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ScopeableRequest](./kibana-plugin-server.scopeablerequest.md) + +## ScopeableRequest type + +A user credentials container. It accommodates the necessary auth credentials to impersonate the current user. + +See [KibanaRequest](./kibana-plugin-server.kibanarequest.md). + +Signature: + +```typescript +export declare type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; +``` diff --git a/docs/limitations.asciidoc b/docs/limitations.asciidoc index 9bcba3b65d6605..818cc766bf6a93 100644 --- a/docs/limitations.asciidoc +++ b/docs/limitations.asciidoc @@ -19,4 +19,4 @@ These {stack} features also have limitations that affect {kib}: include::limitations/nested-objects.asciidoc[] -include::limitations/export-data.asciidoc[] +include::limitations/export-data.asciidoc[] \ No newline at end of file diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 8d9ef515108eda..8e687f641c92be 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -1,17 +1,22 @@ [[index-patterns]] -== Index patterns +== Creating an index pattern -To visualize and explore data in {kib}, you must create an index pattern. -An index pattern tells {kib} which {es} indices contain the data that you want to work with. -An index pattern can match a single index, multiple indices, and a rollup index. +To explore and visualize data in {kib}, you must create an index pattern. +An index pattern tells {kib} which {es} indices contain the data that +you want to work with. +Once you create an index pattern, you're ready to: + +* Interactively explore your data in <>. +* Analyze your data in charts, tables, gauges, tag clouds, and more in <>. +* Show off your data in a <> workpad. +* If your data includes geo data, visualize it with <>. [float] [[index-patterns-read-only-access]] === [xpack]#Read-only access# -If you have insufficient privileges to create or save index patterns, a read-only +If you have insufficient privileges to create or save index patterns, a read-only indicator appears in Kibana. The buttons to create new index patterns or save -existing index patterns are not visible. For more information on granting access to -Kibana see <>. +existing index patterns are not visible. For more information, see <>. [role="screenshot"] image::images/management-index-read-only-badge.png[Example of Index Pattern Management's read only access indicator in Kibana's header] @@ -20,12 +25,9 @@ image::images/management-index-read-only-badge.png[Example of Index Pattern Mana [[settings-create-pattern]] === Create an index pattern -To get started, go to *Management > Kibana > Index Patterns*. You begin with -an overview of your index patterns, including any that were added when you -downloaded sample data sets. - -You can create a standard index pattern, and if a rollup index is detected in the -cluster, a rollup index pattern. +If you are in an app that requires an index pattern, and you don't have one yet, +{kib} prompts you to create one. Or, you can go directly to +*Management > Kibana > Index Patterns*. [role="screenshot"] image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollup index pattern"] @@ -33,83 +35,93 @@ image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollu [float] ==== Standard index pattern -{kib} makes it easy for you to create an index pattern by walking you through -the process. Just start typing in the *Index pattern* field, and {kib} looks for -the names of {es} indices that match your input. Make sure that the name of the +Just start typing in the *Index pattern* field, and {kib} looks for +the names of {es} indices that match your input. Make sure that the name of the index pattern is unique. - -If you want to include system indices in your search, toggle the switch in the -upper right. +To include system indices in your search, toggle the switch in the upper right. [role="screenshot"] image:management/index-patterns/images/create-index-pattern.png["Create index pattern"] -Your index pattern can match multiple {es} indices. -Use a comma to separate the names, with no space after the comma. The notation for -wildcards (`*`) and the ability to "exclude" (`-`) also apply +Your index pattern can match multiple {es} indices. +Use a comma to separate the names, with no space after the comma. The notation for +wildcards (`*`) and the ability to "exclude" (`-`) also apply (for example, `test*,-test3`). -When {kib} detects an index with a timestamp, you’re asked to choose a field to -filter your data by time. If you don’t specify a field, you won’t be able +If {kib} detects an index with a timestamp, you’re asked to choose a field to +filter your data by time. If you don’t specify a field, you won’t be able to use the time filter. -Once you’ve created your index pattern, you can start working with -your {es} data in {kib}. Here are some things to try: -* Interactively explore your data in <>. -* Present your data in charts, tables, gauges, tag clouds, and more in <>. -* Show off your data in a <> presentation. -* If your data includes geo data, visualize it using <>. - -For a walkthrough of creating an index pattern and visualizing the data, -see <>. [float] ==== Rollup index pattern -If a rollup index is detected in the cluster, clicking *Create index pattern* -includes an item for creating a rollup index pattern. You create an -index pattern for rolled up data the same way you do for any data. +If a rollup index is detected in the cluster, clicking *Create index pattern* +includes an item for creating a rollup index pattern. +You can match an index pattern to only rolled up data, or mix both rolled +up and raw data to explore and visualize all data together. +An index pattern can match +only one rollup index. + +[float] +[[management-cross-cluster-search]] +==== {ccs-cap} index pattern + +If your {es} clusters are configured for {ref}/modules-cross-cluster-search.html[{ccs}], you can create +index patterns to search across the clusters of your choosing. Using the +same syntax that you'd use in a raw {ccs} request in {es}, create your +index pattern with the convention `:`. + +For example, to query {ls} indices across two {es} clusters +that you set up for {ccs}, which are named `cluster_one` and `cluster_two`, +you would use `cluster_one:logstash-*,cluster_two:logstash-*` as your index pattern. + +You can use wildcards in your cluster names +to match any number of clusters, so if you want to search {ls} indices across +clusters named `cluster_foo`, `cluster_bar`, and so on, you would use `cluster_*:logstash-*` +as your index pattern. -You can match an index pattern to only rolled up data, or mix both rolled -up and raw data to visualize all data together. An index pattern can match -only one rollup index, not multiple. There is no restriction on the -number of standard indices that an index pattern can match. +To query across all {es} clusters that have been configured for {ccs}, +use a standalone wildcard for your cluster name in your index +pattern: `*:logstash-*`. -See <> -for more detailed information. +Once an index pattern is configured using the {ccs} syntax, all searches and +aggregations using that index pattern in {kib} take advantage of {ccs}. [float] === Manage your index pattern -Once you’ve created an index pattern, you’re presented a table of all fields -and associated data types in the index. +Once you create an index pattern, manually or with a sample data set, +you can look at its fields and associated data types. +You can also perform housekeeping tasks, such as making the +index pattern the default or deleting it when you longer need it. +To drill down into the details of an index pattern, click its name in +the *Index patterns* overview. [role="screenshot"] image:management/index-patterns/images/new-index-pattern.png["Index files and data types"] -You can perform the following actions: +From the detailed view, you can perform the following actions: -* *Manage the index fields.* Click a column header to sort the table by that column. -Use the field dropdown menu to limit to display to a specific field. -See <> for more detailed information. +* *Manage the index fields.* You can add formatters to format values and create +scripted fields. +See <> for more information. -* [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users -aware of which index pattern is the default. The first pattern -you create is automatically designated as the default pattern. The default -index pattern is loaded when you view the Discover tab. +* [[set-default-pattern]]*Set the default index pattern.* {kib} uses a badge to make users +aware of which index pattern is the default. The first pattern +you create is automatically designated as the default pattern. The default +index pattern is loaded when you open *Discover*. -* [[reload-fields]]*Reload the index fields list.* You can reload the index fields list to -pick up any newly-added fields. Doing so also resets Kibana’s popularity counters -for the fields. The popularity counters keep track of the fields -you’ve used most often in {kib} and are used to sort fields in lists. +* [[reload-fields]]*Refresh the index fields list.* You can refresh the index fields list to +pick up any newly-added fields. Doing so also resets Kibana’s popularity counters +for the fields. The popularity counters are used in *Discover* to sort fields in lists. -* [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of -Saved Objects in {kib}. You will not be able to recover field formatters, +* [[delete-pattern]]*Delete the index pattern.* This action removes the pattern from the list of +Saved Objects in {kib}. You will not be able to recover field formatters, scripted fields, source filters, and field popularity data associated with the index pattern. -+ -Deleting an index pattern breaks all visualizations, saved searches, and -other saved objects that reference the pattern. Deleting an index pattern does +Deleting an index pattern does not remove any indices or data documents from {es}. - -include::index-patterns/management-cross-cluster-search.asciidoc[] ++ +WARNING: Deleting an index pattern breaks all visualizations, saved searches, and +other saved objects that reference the pattern. diff --git a/docs/management/index-patterns/management-cross-cluster-search.asciidoc b/docs/management/index-patterns/management-cross-cluster-search.asciidoc deleted file mode 100644 index 9fd8deb7f34be7..00000000000000 --- a/docs/management/index-patterns/management-cross-cluster-search.asciidoc +++ /dev/null @@ -1,30 +0,0 @@ -[[management-cross-cluster-search]] -=== {ccs-cap} - -{es} supports the ability to run search and aggregation requests across multiple -clusters using a module called _{ccs}_. - -In order to take advantage of {ccs}, you must configure your {es} -clusters accordingly. Review the corresponding {es} -{ref}/modules-cross-cluster-search.html[documentation] before attempting to use {ccs} in {kib}. - -Once your {es} clusters are configured for {ccs}, you can create -specific index patterns in {kib} to search across the clusters of your choosing. Using the -same syntax that you'd use in a raw {ccs} request in {es}, create your -index pattern in {kib} with the convention `:`. - -For example, if you want to query {ls} indices across two of the {es} clusters -that you set up for {ccs}, which were named `cluster_one` and `cluster_two`, -you would use `cluster_one:logstash-*,cluster_two:logstash-*` as your index pattern in {kib}. - -Just like in raw search requests in {es}, you can use wildcards in your cluster names -to match any number of clusters, so if you wanted to search {ls} indices across any -clusters named `cluster_foo`, `cluster_bar`, and so on, you would use `cluster_*:logstash-*` -as your index pattern in {kib}. - -If you want to query across all {es} clusters that have been configured for {ccs}, -then use a standalone wildcard for your cluster name in your {kib} index -pattern: `*:logstash-*`. - -Once an index pattern is configured using the {ccs} syntax, all searches and -aggregations using that index pattern in {kib} take advantage of {ccs}. diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 8d28b55a6502fb..a6eeffec51cb02 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -5,9 +5,23 @@ APM settings ++++ -You do not need to configure any settings to use APM. It is enabled by default. -If you'd like to change any of the default values, -copy and paste the relevant settings below into your `kibana.yml` configuration file. +You do not need to configure any settings to use the APM app. It is enabled by default. + +[float] +[[apm-indices-settings-kb]] +==== APM Indices + +// This content is reused in the APM app documentation. +// Any changes made in this file will be seen there as well. +// tag::apm-indices-settings[] + +Index defaults can be changed in Kibana. Navigate to *APM* > *Settings* > *Indices*. +Index settings in the APM app take precedence over those set in `kibana.yml`. + +[role="screenshot"] +image::settings/images/apm-settings.png[APM app settings in Kibana] + +// end::apm-indices-settings[] [float] [[general-apm-settings-kb]] @@ -17,6 +31,9 @@ copy and paste the relevant settings below into your `kibana.yml` configuration // Any changes made in this file will be seen there as well. // tag::general-apm-settings[] +If you'd like to change any of the default values, +copy and paste the relevant settings below into your `kibana.yml` configuration file. + xpack.apm.enabled:: Set to `false` to disabled the APM plugin {kib}. Defaults to `true`. diff --git a/docs/settings/images/apm-settings.png b/docs/settings/images/apm-settings.png new file mode 100644 index 00000000000000..876f135da93564 Binary files /dev/null and b/docs/settings/images/apm-settings.png differ diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 2fc74d2ffee32b..38a46a3cde5a0b 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -66,13 +66,6 @@ both the {es} monitoring cluster and the {es} production cluster. If not set, {kib} uses the value of the `elasticsearch.password` setting. -`telemetry.enabled`:: -Set to `true` (default) to send cluster statistics to Elastic. Reporting your -cluster statistics helps us improve your user experience. Your data is never -shared with anyone. Set to `false` to disable statistics reporting from any -browser connected to the {kib} instance. You can also opt out through the -*Advanced Settings* in {kib}. - `xpack.monitoring.elasticsearch.pingTimeout`:: Specifies the time in milliseconds to wait for {es} to respond to internal health checks. By default, it matches the `elasticsearch.pingTimeout` setting, diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 01e6bd51ea50b7..c98df6ca78356b 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -380,6 +380,11 @@ cannot be `false` at the same time. To enable telemetry and prevent users from disabling it, set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. +`telemetry.enabled`:: *Default: true* Reporting your cluster statistics helps +us improve your user experience. Your data is never shared with anyone. Set to +`false` to disable telemetry capabilities entirely. You can alternatively opt +out through the *Advanced Settings* in {kib}. + `vega.enableExternalUrls:`:: *Default: false* Set this value to true to allow Vega to use any URL to access external data sources and images. If false, Vega can only get data from Elasticsearch. `xpack.license_management.enabled`:: *Default: true* Set this value to false to diff --git a/docs/spaces/images/spaces-configure-landing-page.png b/docs/spaces/images/spaces-configure-landing-page.png new file mode 100644 index 00000000000000..15006594b6d7bc Binary files /dev/null and b/docs/spaces/images/spaces-configure-landing-page.png differ diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 69655aac521e7a..fb5ef670692dc8 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -2,13 +2,13 @@ [[xpack-spaces]] == Spaces -Spaces enable you to organize your dashboards and other saved -objects into meaningful categories. Once inside a space, you see only -the dashboards and saved objects that belong to that space. +Spaces enable you to organize your dashboards and other saved +objects into meaningful categories. Once inside a space, you see only +the dashboards and saved objects that belong to that space. -{kib} creates a default space for you. -After you create your own -spaces, you're asked to choose a space when you log in to Kibana. You can change your +{kib} creates a default space for you. +After you create your own +spaces, you're asked to choose a space when you log in to Kibana. You can change your current space at any time by using the menu in the upper left. [role="screenshot"] @@ -29,24 +29,24 @@ Kibana supports spaces in several ways. You can: [[spaces-managing]] === View, create, and delete spaces -Go to **Management > Spaces** for an overview of your spaces. This view provides actions +Go to **Management > Spaces** for an overview of your spaces. This view provides actions for you to create, edit, and delete spaces. [role="screenshot"] image::spaces/images/space-management.png["Space management"] [float] -==== Create or edit a space +==== Create or edit a space -You can create as many spaces as you like. Click *Create a space* and provide a name, -URL identifier, optional description. +You can create as many spaces as you like. Click *Create a space* and provide a name, +URL identifier, optional description. -The URL identifier is a short text string that becomes part of the -{kib} URL when you are inside that space. {kib} suggests a URL identifier based +The URL identifier is a short text string that becomes part of the +{kib} URL when you are inside that space. {kib} suggests a URL identifier based on the name of your space, but you can customize the identifier to your liking. You cannot change the space identifier once you create the space. -{kib} also has an <> +{kib} also has an <> if you prefer to create spaces programatically. [role="screenshot"] @@ -55,7 +55,7 @@ image::spaces/images/edit-space.png["Space management"] [float] ==== Delete a space -Deleting a space permanently removes the space and all of its contents. +Deleting a space permanently removes the space and all of its contents. Find the space on the *Spaces* overview page and click the trash icon in the Actions column. You can't delete the default space, but you can customize it to your liking. @@ -63,14 +63,14 @@ You can't delete the default space, but you can customize it to your liking. [[spaces-control-feature-visibility]] === Control feature access based on user needs -You have control over which features are visible in each space. -For example, you might hide Dev Tools +You have control over which features are visible in each space. +For example, you might hide Dev Tools in your "Executive" space or show Stack Monitoring only in your "Admin" space. You can define which features to show or hide when you add or edit a space. -Controlling feature -visibility is not a security feature. To secure access -to specific features on a per-user basis, you must configure +Controlling feature +visibility is not a security feature. To secure access +to specific features on a per-user basis, you must configure <>. [role="screenshot"] @@ -80,10 +80,10 @@ image::spaces/images/edit-space-feature-visibility.png["Controlling features vis [[spaces-control-user-access]] === Control feature access based on user privileges -When using Kibana with security, you can configure applications and features -based on your users’ privileges. This means different roles can have access -to different features in the same space. -Power users might have privileges to create and edit visualizations and dashboards, +When using Kibana with security, you can configure applications and features +based on your users’ privileges. This means different roles can have access +to different features in the same space. +Power users might have privileges to create and edit visualizations and dashboards, while analysts or executives might have Dashboard and Canvas with read-only privileges. See <> for details. @@ -106,7 +106,7 @@ interface. . Import your saved objects. . (Optional) Delete objects in the export space that you no longer need. -{kib} also has beta <> and +{kib} also has beta <> and <> APIs if you want to automate this process. [float] @@ -115,17 +115,22 @@ interface. You can create a custom experience for users by configuring the {kib} landing page on a per-space basis. The landing page can route users to a specific dashboard, application, or saved object as they enter each space. -To configure the landing page, use the `defaultRoute` setting in < Advanced settings>>. + +To configure the landing page, use the default route setting in < Advanced settings>>. +For example, you might set the default route to `/app/kibana#/dashboards`. + +[role="screenshot"] +image::spaces/images/spaces-configure-landing-page.png["Configure space-level landing page"] + [float] [[spaces-delete-started]] === Disable and version updates -Spaces are automatically enabled in {kib}. If you don't want use this feature, +Spaces are automatically enabled in {kib}. If you don't want use this feature, you can disable it -by setting `xpack.spaces.enabled` to `false` in your +by setting `xpack.spaces.enabled` to `false` in your `kibana.yml` configuration file. -If you are upgrading your -version of {kib}, the default space will contain all of your existing saved objects. - +If you are upgrading your +version of {kib}, the default space will contain all of your existing saved objects. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 36d6b0a6e473a9..7de7d73bf16642 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -3,6 +3,7 @@ [partintro] -- + When you know what your data includes, you can create visualizations that best display that data and build better dashboards. *Discover* enables you to explore your data, find @@ -99,6 +100,8 @@ or create a direct link to share. The *Save* and *Share* actions are in the men -- +include::{kib-repo-dir}/management/index-patterns.asciidoc[] + include::{kib-repo-dir}/discover/set-time-filter.asciidoc[] include::{kib-repo-dir}/discover/search.asciidoc[] diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index d1acb915f1973f..2c41d0072fe5b9 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -13,8 +13,6 @@ visualizations, and dashboards. include::{kib-repo-dir}/management/managing-licenses.asciidoc[] -include::{kib-repo-dir}/management/index-patterns.asciidoc[] - include::{kib-repo-dir}/management/rollups/create_and_manage_rollups.asciidoc[] include::{kib-repo-dir}/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc[] @@ -40,5 +38,3 @@ include::{kib-repo-dir}/management/managing-beats.asciidoc[] include::{kib-repo-dir}/management/managing-remote-clusters.asciidoc[] include::{kib-repo-dir}/management/snapshot-restore/index.asciidoc[] - - diff --git a/examples/demo_search/server/plugin.ts b/examples/demo_search/server/plugin.ts index 23c82225563c89..653aa217717fa2 100644 --- a/examples/demo_search/server/plugin.ts +++ b/examples/demo_search/server/plugin.ts @@ -18,7 +18,7 @@ */ import { Plugin, CoreSetup, PluginInitializerContext } from 'kibana/server'; -import { DataPluginSetup } from 'src/plugins/data/server/plugin'; +import { PluginSetup as DataPluginSetup } from 'src/plugins/data/server'; import { demoSearchStrategyProvider } from './demo_search_strategy'; import { DEMO_SEARCH_STRATEGY, IDemoRequest, IDemoResponse } from '../common'; diff --git a/package.json b/package.json index 99151f33962c44..db5764e6e91bac 100644 --- a/package.json +++ b/package.json @@ -240,7 +240,7 @@ "react-use": "^13.13.0", "reactcss": "1.2.3", "redux": "4.0.0", - "redux-actions": "2.2.1", + "redux-actions": "2.6.5", "redux-thunk": "2.3.0", "regenerator-runtime": "^0.13.3", "regression": "2.0.1", @@ -314,7 +314,7 @@ "@types/delete-empty": "^2.0.0", "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.9.0", - "@types/eslint": "^6.1.2", + "@types/eslint": "^6.1.3", "@types/fetch-mock": "^7.3.1", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.1", @@ -353,7 +353,7 @@ "@types/react-router-dom": "^5.1.3", "@types/react-virtualized": "^9.18.7", "@types/redux": "^3.6.31", - "@types/redux-actions": "^2.2.1", + "@types/redux-actions": "^2.6.1", "@types/request": "^2.48.2", "@types/selenium-webdriver": "^4.0.5", "@types/semver": "^5.5.0", @@ -368,8 +368,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.12.0", - "@typescript-eslint/parser": "^2.12.0", + "@typescript-eslint/eslint-plugin": "^2.15.0", + "@typescript-eslint/parser": "^2.15.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", @@ -389,21 +389,21 @@ "enzyme-adapter-react-16": "^1.15.1", "enzyme-adapter-utils": "^1.12.1", "enzyme-to-json": "^3.4.3", - "eslint": "^6.5.1", - "eslint-config-prettier": "^6.4.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.9.0", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-ban": "^1.3.0", - "eslint-plugin-cypress": "^2.7.0", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.19.0", + "eslint-plugin-ban": "^1.4.0", + "eslint-plugin-cypress": "^2.8.1", + "eslint-plugin-import": "^2.19.1", + "eslint-plugin-jest": "^23.3.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-mocha": "^6.2.0", + "eslint-plugin-mocha": "^6.2.2", "eslint-plugin-no-unsanitized": "^3.0.2", - "eslint-plugin-node": "^10.0.0", + "eslint-plugin-node": "^11.0.0", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-prettier": "^3.1.1", - "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.1.2", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-hooks": "^2.3.0", "exit-hook": "^2.2.0", "faker": "1.1.0", "fetch-mock": "^7.3.9", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 04602d196a7f36..34b1b0fec376f6 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,19 +15,19 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.12.0", - "@typescript-eslint/parser": "^2.12.0", + "@typescript-eslint/eslint-plugin": "^2.15.0", + "@typescript-eslint/parser": "^2.15.0", "babel-eslint": "^10.0.3", - "eslint": "^6.5.1", + "eslint": "^6.8.0", "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-ban": "^1.3.0", + "eslint-plugin-ban": "^1.4.0", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.19.0", - "eslint-plugin-mocha": "^6.2.0", + "eslint-plugin-import": "^2.19.1", + "eslint-plugin-jest": "^23.3.0", + "eslint-plugin-mocha": "^6.2.2", "eslint-plugin-no-unsanitized": "^3.0.2", "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-react": "^7.16.0", - "eslint-plugin-react-hooks": "^2.1.2" + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-hooks": "^2.3.0" } } diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index fd62f1b3c03b22..e6f3e60128983d 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -156,6 +156,9 @@ __Usage:__ const valueSchema = schema.boolean({ defaultValue: false }); ``` +__Notes:__ +* The `schema.boolean()` also supports a string as input if it equals `'true'` or `'false'` (case-insensitive). + #### `schema.literal()` Validates input data as a [string](https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types), [numeric](https://www.typescriptlang.org/docs/handbook/advanced-types.html#numeric-literal-types) or boolean literal. @@ -397,7 +400,7 @@ const valueSchema = schema.byteSize({ min: '3kb' }); ``` __Notes:__ -* The string value for `schema.byteSize()` and its options supports the following prefixes: `b`, `kb`, `mb`, `gb` and `tb`. +* The string value for `schema.byteSize()` and its options supports the following optional suffixes: `b`, `kb`, `mb`, `gb` and `tb`. The default suffix is `b`. * The number value is treated as a number of bytes and hence should be a positive integer, e.g. `100` is equal to `'100b'`. * Currently you cannot specify zero bytes with a string format and should use number `0` instead. @@ -417,7 +420,7 @@ const valueSchema = schema.duration({ defaultValue: '70ms' }); ``` __Notes:__ -* The string value for `schema.duration()` supports the following prefixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `Y`. +* The string value for `schema.duration()` supports the following optional suffixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `Y`. The default suffix is `ms`. * The number value is treated as a number of milliseconds and hence should be a positive integer, e.g. `100` is equal to `'100ms'`. #### `schema.conditional()` diff --git a/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap b/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap index 1db6930062a9a1..97e9082401b3da 100644 --- a/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap +++ b/packages/kbn-config-schema/src/byte_size_value/__snapshots__/index.test.ts.snap @@ -1,9 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`#constructor throws if number of bytes is negative 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-1024]"`; +exports[`#constructor throws if number of bytes is negative 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-1024]."`; -exports[`#constructor throws if number of bytes is not safe 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]"`; +exports[`#constructor throws if number of bytes is not safe 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]."`; -exports[`#constructor throws if number of bytes is not safe 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]"`; +exports[`#constructor throws if number of bytes is not safe 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]."`; -exports[`#constructor throws if number of bytes is not safe 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]"`; +exports[`#constructor throws if number of bytes is not safe 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]."`; + +exports[`parsing units throws an error when unsupported unit specified 1`] = `"Failed to parse [1tb] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] (e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer."`; diff --git a/packages/kbn-config-schema/src/byte_size_value/index.test.ts b/packages/kbn-config-schema/src/byte_size_value/index.test.ts index 46ed96c83dd1f7..198d95aa0ab4cc 100644 --- a/packages/kbn-config-schema/src/byte_size_value/index.test.ts +++ b/packages/kbn-config-schema/src/byte_size_value/index.test.ts @@ -20,6 +20,10 @@ import { ByteSizeValue } from '.'; describe('parsing units', () => { + test('number string (bytes)', () => { + expect(ByteSizeValue.parse('123').getValueInBytes()).toBe(123); + }); + test('bytes', () => { expect(ByteSizeValue.parse('123b').getValueInBytes()).toBe(123); }); @@ -37,12 +41,8 @@ describe('parsing units', () => { expect(ByteSizeValue.parse('1gb').getValueInBytes()).toBe(1073741824); }); - test('throws an error when no unit specified', () => { - expect(() => ByteSizeValue.parse('123')).toThrowError('could not parse byte size value'); - }); - test('throws an error when unsupported unit specified', () => { - expect(() => ByteSizeValue.parse('1tb')).toThrowError('could not parse byte size value'); + expect(() => ByteSizeValue.parse('1tb')).toThrowErrorMatchingSnapshot(); }); }); diff --git a/packages/kbn-config-schema/src/byte_size_value/index.ts b/packages/kbn-config-schema/src/byte_size_value/index.ts index fb0105503a1494..48862821bb78d9 100644 --- a/packages/kbn-config-schema/src/byte_size_value/index.ts +++ b/packages/kbn-config-schema/src/byte_size_value/index.ts @@ -35,9 +35,14 @@ export class ByteSizeValue { public static parse(text: string): ByteSizeValue { const match = /([1-9][0-9]*)(b|kb|mb|gb)/.exec(text); if (!match) { - throw new Error( - `could not parse byte size value [${text}]. Value must be a safe positive integer.` - ); + const number = Number(text); + if (typeof number !== 'number' || isNaN(number)) { + throw new Error( + `Failed to parse [${text}] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] ` + + `(e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer.` + ); + } + return new ByteSizeValue(number); } const value = parseInt(match[1], 0); @@ -49,8 +54,7 @@ export class ByteSizeValue { constructor(private readonly valueInBytes: number) { if (!Number.isSafeInteger(valueInBytes) || valueInBytes < 0) { throw new Error( - `Value in bytes is expected to be a safe positive integer, ` + - `but provided [${valueInBytes}]` + `Value in bytes is expected to be a safe positive integer, but provided [${valueInBytes}].` ); } } diff --git a/packages/kbn-config-schema/src/duration/index.ts b/packages/kbn-config-schema/src/duration/index.ts index ff8f96614a193b..b96b5a3687bbb4 100644 --- a/packages/kbn-config-schema/src/duration/index.ts +++ b/packages/kbn-config-schema/src/duration/index.ts @@ -25,10 +25,14 @@ const timeFormatRegex = /^(0|[1-9][0-9]*)(ms|s|m|h|d|w|M|Y)$/; function stringToDuration(text: string) { const result = timeFormatRegex.exec(text); if (!result) { - throw new Error( - `Failed to parse [${text}] as time value. ` + - `Format must be [ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y')` - ); + const number = Number(text); + if (typeof number !== 'number' || isNaN(number)) { + throw new Error( + `Failed to parse [${text}] as time value. Value must be a duration in milliseconds, or follow the format ` + + `[ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer.` + ); + } + return numberToDuration(number); } const count = parseInt(result[1], 0); @@ -40,8 +44,7 @@ function stringToDuration(text: string) { function numberToDuration(numberMs: number) { if (!Number.isSafeInteger(numberMs) || numberMs < 0) { throw new Error( - `Failed to parse [${numberMs}] as time value. ` + - `Value should be a safe positive integer number.` + `Value in milliseconds is expected to be a safe positive integer, but provided [${numberMs}].` ); } diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index 4d5091eaa09b13..044c3050f9fa87 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -82,7 +82,23 @@ export const internals = Joi.extend([ base: Joi.boolean(), coerce(value: any, state: State, options: ValidationOptions) { // If value isn't defined, let Joi handle default value if it's defined. - if (value !== undefined && typeof value !== 'boolean') { + if (value === undefined) { + return value; + } + + // Allow strings 'true' and 'false' to be coerced to booleans (case-insensitive). + + // From Joi docs on `Joi.boolean`: + // > Generates a schema object that matches a boolean data type. Can also + // > be called via bool(). If the validation convert option is on + // > (enabled by default), a string (either "true" or "false") will be + // converted to a boolean if specified. + if (typeof value === 'string') { + const normalized = value.toLowerCase(); + value = normalized === 'true' ? true : normalized === 'false' ? false : value; + } + + if (typeof value !== 'boolean') { return this.createError('boolean.base', { value }, state, options); } diff --git a/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap index c3f33dc29bf50e..0e5f6de2deea8f 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/boolean_type.test.ts.snap @@ -9,3 +9,7 @@ exports[`returns error when not boolean 1`] = `"expected value of type [boolean] exports[`returns error when not boolean 2`] = `"expected value of type [boolean] but got [Array]"`; exports[`returns error when not boolean 3`] = `"expected value of type [boolean] but got [string]"`; + +exports[`returns error when not boolean 4`] = `"expected value of type [boolean] but got [number]"`; + +exports[`returns error when not boolean 5`] = `"expected value of type [boolean] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap index f6f45a96ca1612..ea2102b1776fbf 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/byte_size_type.test.ts.snap @@ -18,6 +18,12 @@ ByteSizeValue { } `; +exports[`#defaultValue can be a string-formatted number 1`] = ` +ByteSizeValue { + "valueInBytes": 1024, +} +`; + exports[`#max returns error when larger 1`] = `"Value is [1mb] ([1048576b]) but it must be equal to or less than [1kb]"`; exports[`#max returns value when smaller 1`] = ` @@ -38,20 +44,18 @@ exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value o exports[`is required by default 1`] = `"expected value of type [ByteSize] but got [undefined]"`; -exports[`returns error when not string or positive safe integer 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-123]"`; +exports[`returns error when not valid string or positive safe integer 1`] = `"Value in bytes is expected to be a safe positive integer, but provided [-123]."`; -exports[`returns error when not string or positive safe integer 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]"`; +exports[`returns error when not valid string or positive safe integer 2`] = `"Value in bytes is expected to be a safe positive integer, but provided [NaN]."`; -exports[`returns error when not string or positive safe integer 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]"`; +exports[`returns error when not valid string or positive safe integer 3`] = `"Value in bytes is expected to be a safe positive integer, but provided [Infinity]."`; -exports[`returns error when not string or positive safe integer 4`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]"`; +exports[`returns error when not valid string or positive safe integer 4`] = `"Value in bytes is expected to be a safe positive integer, but provided [9007199254740992]."`; -exports[`returns error when not string or positive safe integer 5`] = `"expected value of type [ByteSize] but got [Array]"`; +exports[`returns error when not valid string or positive safe integer 5`] = `"expected value of type [ByteSize] but got [Array]"`; -exports[`returns error when not string or positive safe integer 6`] = `"expected value of type [ByteSize] but got [RegExp]"`; +exports[`returns error when not valid string or positive safe integer 6`] = `"expected value of type [ByteSize] but got [RegExp]"`; -exports[`returns value by default 1`] = ` -ByteSizeValue { - "valueInBytes": 123, -} -`; +exports[`returns error when not valid string or positive safe integer 7`] = `"Failed to parse [123foo] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] (e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer."`; + +exports[`returns error when not valid string or positive safe integer 8`] = `"Failed to parse [123 456] as byte value. Value must be either number of bytes, or follow the format [b|kb|mb|gb] (e.g., '1024kb', '200mb', '1gb'), where the number is a safe positive integer."`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap index a21c28e7cc6142..c4e4ff652a2d7e 100644 --- a/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap +++ b/packages/kbn-config-schema/src/types/__snapshots__/duration_type.test.ts.snap @@ -6,20 +6,24 @@ exports[`#defaultValue can be a number 1`] = `"PT0.6S"`; exports[`#defaultValue can be a string 1`] = `"PT1H"`; +exports[`#defaultValue can be a string-formatted number 1`] = `"PT0.6S"`; + exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [moment.Duration] but got [undefined]"`; exports[`is required by default 1`] = `"expected value of type [moment.Duration] but got [undefined]"`; -exports[`returns error when not string or non-safe positive integer 1`] = `"Failed to parse [-123] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 1`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [-123]."`; + +exports[`returns error when not valid string or non-safe positive integer 2`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [NaN]."`; -exports[`returns error when not string or non-safe positive integer 2`] = `"Failed to parse [NaN] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 3`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [Infinity]."`; -exports[`returns error when not string or non-safe positive integer 3`] = `"Failed to parse [Infinity] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 4`] = `"Value in milliseconds is expected to be a safe positive integer, but provided [9007199254740992]."`; -exports[`returns error when not string or non-safe positive integer 4`] = `"Failed to parse [9007199254740992] as time value. Value should be a safe positive integer number."`; +exports[`returns error when not valid string or non-safe positive integer 5`] = `"expected value of type [moment.Duration] but got [Array]"`; -exports[`returns error when not string or non-safe positive integer 5`] = `"expected value of type [moment.Duration] but got [Array]"`; +exports[`returns error when not valid string or non-safe positive integer 6`] = `"expected value of type [moment.Duration] but got [RegExp]"`; -exports[`returns error when not string or non-safe positive integer 6`] = `"expected value of type [moment.Duration] but got [RegExp]"`; +exports[`returns error when not valid string or non-safe positive integer 7`] = `"Failed to parse [123foo] as time value. Value must be a duration in milliseconds, or follow the format [ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer."`; -exports[`returns value by default 1`] = `"PT2M3S"`; +exports[`returns error when not valid string or non-safe positive integer 8`] = `"Failed to parse [123 456] as time value. Value must be a duration in milliseconds, or follow the format [ms|s|m|h|d|w|M|Y] (e.g. '70ms', '5s', '3d', '1Y'), where the duration is a safe positive integer."`; diff --git a/packages/kbn-config-schema/src/types/boolean_type.test.ts b/packages/kbn-config-schema/src/types/boolean_type.test.ts index d6e274f05e3ffa..e94999b5054377 100644 --- a/packages/kbn-config-schema/src/types/boolean_type.test.ts +++ b/packages/kbn-config-schema/src/types/boolean_type.test.ts @@ -23,6 +23,17 @@ test('returns value by default', () => { expect(schema.boolean().validate(true)).toBe(true); }); +test('handles boolean strings', () => { + expect(schema.boolean().validate('true')).toBe(true); + expect(schema.boolean().validate('TRUE')).toBe(true); + expect(schema.boolean().validate('True')).toBe(true); + expect(schema.boolean().validate('TrUe')).toBe(true); + expect(schema.boolean().validate('false')).toBe(false); + expect(schema.boolean().validate('FALSE')).toBe(false); + expect(schema.boolean().validate('False')).toBe(false); + expect(schema.boolean().validate('FaLse')).toBe(false); +}); + test('is required by default', () => { expect(() => schema.boolean().validate(undefined)).toThrowErrorMatchingSnapshot(); }); @@ -49,4 +60,8 @@ test('returns error when not boolean', () => { expect(() => schema.boolean().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => schema.boolean().validate('abc')).toThrowErrorMatchingSnapshot(); + + expect(() => schema.boolean().validate(0)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.boolean().validate('no')).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/kbn-config-schema/src/types/byte_size_type.test.ts b/packages/kbn-config-schema/src/types/byte_size_type.test.ts index 67eae1e7c382a6..7c65ec2945b493 100644 --- a/packages/kbn-config-schema/src/types/byte_size_type.test.ts +++ b/packages/kbn-config-schema/src/types/byte_size_type.test.ts @@ -23,7 +23,15 @@ import { ByteSizeValue } from '../byte_size_value'; const { byteSize } = schema; test('returns value by default', () => { - expect(byteSize().validate('123b')).toMatchSnapshot(); + expect(byteSize().validate('123b')).toEqual(new ByteSizeValue(123)); +}); + +test('handles numeric strings', () => { + expect(byteSize().validate('123')).toEqual(new ByteSizeValue(123)); +}); + +test('handles numbers', () => { + expect(byteSize().validate(123)).toEqual(new ByteSizeValue(123)); }); test('is required by default', () => { @@ -51,6 +59,14 @@ describe('#defaultValue', () => { ).toMatchSnapshot(); }); + test('can be a string-formatted number', () => { + expect( + byteSize({ + defaultValue: '1024', + }).validate(undefined) + ).toMatchSnapshot(); + }); + test('can be a number', () => { expect( byteSize({ @@ -88,7 +104,7 @@ describe('#max', () => { }); }); -test('returns error when not string or positive safe integer', () => { +test('returns error when not valid string or positive safe integer', () => { expect(() => byteSize().validate(-123)).toThrowErrorMatchingSnapshot(); expect(() => byteSize().validate(NaN)).toThrowErrorMatchingSnapshot(); @@ -100,4 +116,8 @@ test('returns error when not string or positive safe integer', () => { expect(() => byteSize().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => byteSize().validate(/abc/)).toThrowErrorMatchingSnapshot(); + + expect(() => byteSize().validate('123foo')).toThrowErrorMatchingSnapshot(); + + expect(() => byteSize().validate('123 456')).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/kbn-config-schema/src/types/duration_type.test.ts b/packages/kbn-config-schema/src/types/duration_type.test.ts index 39655d43d7b75b..09e92ce727f2a2 100644 --- a/packages/kbn-config-schema/src/types/duration_type.test.ts +++ b/packages/kbn-config-schema/src/types/duration_type.test.ts @@ -23,7 +23,15 @@ import { schema } from '..'; const { duration, object, contextRef, siblingRef } = schema; test('returns value by default', () => { - expect(duration().validate('123s')).toMatchSnapshot(); + expect(duration().validate('123s')).toEqual(momentDuration(123000)); +}); + +test('handles numeric string', () => { + expect(duration().validate('123000')).toEqual(momentDuration(123000)); +}); + +test('handles number', () => { + expect(duration().validate(123000)).toEqual(momentDuration(123000)); }); test('is required by default', () => { @@ -51,6 +59,14 @@ describe('#defaultValue', () => { ).toMatchSnapshot(); }); + test('can be a string-formatted number', () => { + expect( + duration({ + defaultValue: '600', + }).validate(undefined) + ).toMatchSnapshot(); + }); + test('can be a number', () => { expect( duration({ @@ -124,7 +140,7 @@ Object { }); }); -test('returns error when not string or non-safe positive integer', () => { +test('returns error when not valid string or non-safe positive integer', () => { expect(() => duration().validate(-123)).toThrowErrorMatchingSnapshot(); expect(() => duration().validate(NaN)).toThrowErrorMatchingSnapshot(); @@ -136,4 +152,8 @@ test('returns error when not string or non-safe positive integer', () => { expect(() => duration().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); expect(() => duration().validate(/abc/)).toThrowErrorMatchingSnapshot(); + + expect(() => duration().validate('123foo')).toThrowErrorMatchingSnapshot(); + + expect(() => duration().validate('123 456')).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/kbn-dev-utils/src/run/README.md b/packages/kbn-dev-utils/src/run/README.md index 913b601058f870..99893a62376689 100644 --- a/packages/kbn-dev-utils/src/run/README.md +++ b/packages/kbn-dev-utils/src/run/README.md @@ -117,7 +117,7 @@ $ node scripts/my_task - *`flags.allowUnexpected: boolean`* - By default, any flag that is passed but not mentioned in `flags.string`, `flags.boolean`, `flags.alias` or `flags.default` will trigger an error, preventing the run function from calling its first argument. If you have a reason to disable this behavior set this option to `true`. + By default, any flag that is passed but not mentioned in `flags.string`, `flags.boolean`, `flags.alias` or `flags.default` will trigger an error, preventing the run function from calling its first argument. If you have a reason to disable this behavior set this option to `true`. Unexpected flags will be collected from argv into `flags.unexpected`. To parse these flags and guess at their types, you can additionally pass `flags.guessTypesForUnexpectedFlags` but that's not recommended. - ***`createFailError(reason: string, options: { exitCode: number, showHelp: boolean }): FailError`*** diff --git a/packages/kbn-dev-utils/src/run/flags.test.ts b/packages/kbn-dev-utils/src/run/flags.test.ts new file mode 100644 index 00000000000000..c730067a84f46a --- /dev/null +++ b/packages/kbn-dev-utils/src/run/flags.test.ts @@ -0,0 +1,94 @@ +/* + * 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 { getFlags } from './flags'; + +it('gets flags correctly', () => { + expect( + getFlags(['-a', '--abc=bcd', '--foo=bar', '--no-bar', '--foo=baz', '--box', 'yes', '-zxy'], { + flags: { + boolean: ['x'], + string: ['abc'], + alias: { + x: 'extra', + }, + allowUnexpected: true, + }, + }) + ).toMatchInlineSnapshot(` + Object { + "_": Array [], + "abc": "bcd", + "debug": false, + "extra": true, + "help": false, + "quiet": false, + "silent": false, + "unexpected": Array [ + "-a", + "--foo=bar", + "--foo=baz", + "--no-bar", + "--box", + "yes", + "-z", + "-y", + ], + "v": false, + "verbose": false, + "x": true, + } + `); +}); + +it('guesses types for unexpected flags', () => { + expect( + getFlags(['-abc', '--abc=bcd', '--no-foo', '--bar'], { + flags: { + allowUnexpected: true, + guessTypesForUnexpectedFlags: true, + }, + }) + ).toMatchInlineSnapshot(` + Object { + "_": Array [], + "a": true, + "abc": "bcd", + "b": true, + "bar": true, + "c": true, + "debug": false, + "foo": false, + "help": false, + "quiet": false, + "silent": false, + "unexpected": Array [ + "-a", + "-b", + "-c", + "-abc", + "--abc=bcd", + "--no-foo", + "--bar", + ], + "v": false, + "verbose": false, + } + `); +}); diff --git a/packages/kbn-dev-utils/src/run/flags.ts b/packages/kbn-dev-utils/src/run/flags.ts index 6a2966359ece12..c809a40d8512ba 100644 --- a/packages/kbn-dev-utils/src/run/flags.ts +++ b/packages/kbn-dev-utils/src/run/flags.ts @@ -37,7 +37,7 @@ export interface Flags { } export function getFlags(argv: string[], options: Options): Flags { - const unexpected: string[] = []; + const unexpectedNames = new Set(); const flagOpts = options.flags || {}; const { verbose, quiet, silent, debug, help, _, ...others } = getopts(argv, { @@ -49,15 +49,64 @@ export function getFlags(argv: string[], options: Options): Flags { }, default: flagOpts.default, unknown: (name: string) => { - unexpected.push(name); + unexpectedNames.add(name); + return flagOpts.guessTypesForUnexpectedFlags; + }, + } as any); + + const unexpected: string[] = []; + for (const unexpectedName of unexpectedNames) { + const matchingArgv: string[] = []; + + iterArgv: for (const [i, v] of argv.entries()) { + for (const prefix of ['--', '-']) { + if (v.startsWith(prefix)) { + // -/--name=value + if (v.startsWith(`${prefix}${unexpectedName}=`)) { + matchingArgv.push(v); + continue iterArgv; + } + + // -/--name (value possibly follows) + if (v === `${prefix}${unexpectedName}`) { + matchingArgv.push(v); - if (options.flags && options.flags.allowUnexpected) { - return true; + // value follows -/--name + if (argv.length > i + 1 && !argv[i + 1].startsWith('-')) { + matchingArgv.push(argv[i + 1]); + } + + continue iterArgv; + } + } } - return false; - }, - } as any); + // special case for `--no-{flag}` disabling of boolean flags + if (v === `--no-${unexpectedName}`) { + matchingArgv.push(v); + continue iterArgv; + } + + // special case for shortcut flags formatted as `-abc` where `a`, `b`, + // and `c` will be three separate unexpected flags + if ( + unexpectedName.length === 1 && + v[0] === '-' && + v[1] !== '-' && + !v.includes('=') && + v.includes(unexpectedName) + ) { + matchingArgv.push(`-${unexpectedName}`); + continue iterArgv; + } + } + + if (matchingArgv.length) { + unexpected.push(...matchingArgv); + } else { + throw new Error(`unable to find unexpected flag named "${unexpectedName}"`); + } + } return { verbose, @@ -75,7 +124,7 @@ export function getHelp(options: Options) { const usage = options.usage || `node ${relative(process.cwd(), process.argv[1])}`; const optionHelp = ( - dedent((options.flags && options.flags.help) || '') + + dedent(options?.flags?.help || '') + '\n' + dedent` --verbose, -v Log verbosely diff --git a/packages/kbn-dev-utils/src/run/run.ts b/packages/kbn-dev-utils/src/run/run.ts index 06746c663b917c..1d28d435757297 100644 --- a/packages/kbn-dev-utils/src/run/run.ts +++ b/packages/kbn-dev-utils/src/run/run.ts @@ -36,6 +36,7 @@ export interface Options { description?: string; flags?: { allowUnexpected?: boolean; + guessTypesForUnexpectedFlags?: boolean; help?: string; alias?: { [key: string]: string | string[] }; boolean?: string[]; @@ -46,7 +47,6 @@ export interface Options { export async function run(fn: RunFn, options: Options = {}) { const flags = getFlags(process.argv.slice(2), options); - const allowUnexpected = options.flags ? options.flags.allowUnexpected : false; if (flags.help) { process.stderr.write(getHelp(options)); @@ -97,7 +97,7 @@ export async function run(fn: RunFn, options: Options = {}) { const cleanupTasks: CleanupTask[] = [unhookExit]; try { - if (!allowUnexpected && flags.unexpected.length) { + if (!options.flags?.allowUnexpected && flags.unexpected.length) { throw createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`); } diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index 4c519a609d86fe..0ec058eeb8a280 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "outDir": "target", "target": "ES2019", - "declaration": true + "declaration": true, + "declarationMap": true }, "include": [ "src/**/*" diff --git a/packages/kbn-eslint-plugin-eslint/package.json b/packages/kbn-eslint-plugin-eslint/package.json index badcf13187cafb..026938213ac83a 100644 --- a/packages/kbn-eslint-plugin-eslint/package.json +++ b/packages/kbn-eslint-plugin-eslint/package.json @@ -4,12 +4,12 @@ "private": true, "license": "Apache-2.0", "peerDependencies": { - "eslint": "6.5.1", + "eslint": "6.8.0", "babel-eslint": "^10.0.3" }, "dependencies": { "micromatch": "3.1.10", "dedent": "^0.7.0", - "eslint-module-utils": "2.4.1" + "eslint-module-utils": "2.5.0" } } diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 7c5937af441a2e..c3f3f2f477fdd0 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -37054,8 +37054,8 @@ const tooling_log_1 = __webpack_require__(415); const fail_1 = __webpack_require__(425); const flags_1 = __webpack_require__(426); async function run(fn, options = {}) { + var _a; const flags = flags_1.getFlags(process.argv.slice(2), options); - const allowUnexpected = options.flags ? options.flags.allowUnexpected : false; if (flags.help) { process.stderr.write(flags_1.getHelp(options)); process.exit(1); @@ -37098,7 +37098,7 @@ async function run(fn, options = {}) { const unhookExit = exit_hook_1.default(doCleanup); const cleanupTasks = [unhookExit]; try { - if (!allowUnexpected && flags.unexpected.length) { + if (!((_a = options.flags) === null || _a === void 0 ? void 0 : _a.allowUnexpected) && flags.unexpected.length) { throw fail_1.createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`); } try { @@ -37218,7 +37218,7 @@ const path_1 = __webpack_require__(16); const dedent_1 = tslib_1.__importDefault(__webpack_require__(14)); const getopts_1 = tslib_1.__importDefault(__webpack_require__(427)); function getFlags(argv, options) { - const unexpected = []; + const unexpectedNames = new Set(); const flagOpts = options.flags || {}; const { verbose, quiet, silent, debug, help, _, ...others } = getopts_1.default(argv, { string: flagOpts.string, @@ -37229,13 +37229,55 @@ function getFlags(argv, options) { }, default: flagOpts.default, unknown: (name) => { - unexpected.push(name); - if (options.flags && options.flags.allowUnexpected) { - return true; - } - return false; + unexpectedNames.add(name); + return flagOpts.guessTypesForUnexpectedFlags; }, }); + const unexpected = []; + for (const unexpectedName of unexpectedNames) { + const matchingArgv = []; + iterArgv: for (const [i, v] of argv.entries()) { + for (const prefix of ['--', '-']) { + if (v.startsWith(prefix)) { + // -/--name=value + if (v.startsWith(`${prefix}${unexpectedName}=`)) { + matchingArgv.push(v); + continue iterArgv; + } + // -/--name (value possibly follows) + if (v === `${prefix}${unexpectedName}`) { + matchingArgv.push(v); + // value follows -/--name + if (argv.length > i + 1 && !argv[i + 1].startsWith('-')) { + matchingArgv.push(argv[i + 1]); + } + continue iterArgv; + } + } + } + // special case for `--no-{flag}` disabling of boolean flags + if (v === `--no-${unexpectedName}`) { + matchingArgv.push(v); + continue iterArgv; + } + // special case for shortcut flags formatted as `-abc` where `a`, `b`, + // and `c` will be three separate unexpected flags + if (unexpectedName.length === 1 && + v[0] === '-' && + v[1] !== '-' && + !v.includes('=') && + v.includes(unexpectedName)) { + matchingArgv.push(`-${unexpectedName}`); + continue iterArgv; + } + } + if (matchingArgv.length) { + unexpected.push(...matchingArgv); + } + else { + throw new Error(`unable to find unexpected flag named "${unexpectedName}"`); + } + } return { verbose, quiet, @@ -37249,8 +37291,9 @@ function getFlags(argv, options) { } exports.getFlags = getFlags; function getHelp(options) { + var _a, _b; const usage = options.usage || `node ${path_1.relative(process.cwd(), process.argv[1])}`; - const optionHelp = (dedent_1.default((options.flags && options.flags.help) || '') + + const optionHelp = (dedent_1.default(((_b = (_a = options) === null || _a === void 0 ? void 0 : _a.flags) === null || _b === void 0 ? void 0 : _b.help) || '') + '\n' + dedent_1.default ` --verbose, -v Log verbosely @@ -79815,7 +79858,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__(909); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(914); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -79997,8 +80040,8 @@ const EventEmitter = __webpack_require__(379); const path = __webpack_require__(16); const arrify = __webpack_require__(706); const globby = __webpack_require__(707); -const cpFile = __webpack_require__(900); -const CpyError = __webpack_require__(907); +const cpFile = __webpack_require__(905); +const CpyError = __webpack_require__(912); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -80127,8 +80170,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(708); const glob = __webpack_require__(502); const fastGlob = __webpack_require__(710); -const dirGlob = __webpack_require__(893); -const gitignore = __webpack_require__(896); +const dirGlob = __webpack_require__(898); +const gitignore = __webpack_require__(901); const DEFAULT_FILTER = () => false; @@ -80379,11 +80422,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(712); var taskManager = __webpack_require__(713); -var reader_async_1 = __webpack_require__(864); -var reader_stream_1 = __webpack_require__(888); -var reader_sync_1 = __webpack_require__(889); -var arrayUtils = __webpack_require__(891); -var streamUtils = __webpack_require__(892); +var reader_async_1 = __webpack_require__(869); +var reader_stream_1 = __webpack_require__(893); +var reader_sync_1 = __webpack_require__(894); +var arrayUtils = __webpack_require__(896); +var streamUtils = __webpack_require__(897); /** * Synchronous API. */ @@ -81023,9 +81066,9 @@ var extend = __webpack_require__(830); */ var compilers = __webpack_require__(833); -var parsers = __webpack_require__(860); -var cache = __webpack_require__(861); -var utils = __webpack_require__(862); +var parsers = __webpack_require__(865); +var cache = __webpack_require__(866); +var utils = __webpack_require__(867); var MAX_LENGTH = 1024 * 64; /** @@ -99558,9 +99601,9 @@ var toRegex = __webpack_require__(721); */ var compilers = __webpack_require__(850); -var parsers = __webpack_require__(856); -var Extglob = __webpack_require__(859); -var utils = __webpack_require__(858); +var parsers = __webpack_require__(861); +var Extglob = __webpack_require__(864); +var utils = __webpack_require__(863); var MAX_LENGTH = 1024 * 64; /** @@ -100070,7 +100113,7 @@ var parsers = __webpack_require__(854); * Module dependencies */ -var debug = __webpack_require__(793)('expand-brackets'); +var debug = __webpack_require__(856)('expand-brackets'); var extend = __webpack_require__(730); var Snapdragon = __webpack_require__(760); var toRegex = __webpack_require__(721); @@ -100664,12 +100707,839 @@ exports.createRegex = function(pattern, include) { /* 856 */ /***/ (function(module, exports, __webpack_require__) { +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ + +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(857); +} else { + module.exports = __webpack_require__(860); +} + + +/***/ }), +/* 857 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(858); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = 'undefined' != typeof chrome + && 'undefined' != typeof chrome.storage + ? chrome.storage.local + : localstorage(); + +/** + * Colors. + */ + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { + return true; + } + + // is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; + + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + var useColors = this.useColors; + + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return; + + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') + + // the final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if ('%%' === match) return; + index++; + if ('%c' === match) { + // we only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ + +function log() { + // this hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return 'object' === typeof console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} + + +/***/ }), +/* 858 */ +/***/ (function(module, exports, __webpack_require__) { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(859); + +/** + * The currently active debug mode names, and names to skip. + */ + +exports.names = []; +exports.skips = []; + +/** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + +exports.formatters = {}; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * @param {String} namespace + * @return {Number} + * @api private + */ + +function selectColor(namespace) { + var hash = 0, i; + + for (i in namespace) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return exports.colors[Math.abs(hash) % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + +function createDebug(namespace) { + + function debug() { + // disabled? + if (!debug.enabled) return; + + var self = debug; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // turn the `arguments` into a proper Array + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); + } + + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + // if we encounter an escaped % then don't increase the array index + if (match === '%%') return match; + index++; + var formatter = exports.formatters[format]; + if ('function' === typeof formatter) { + var val = args[index]; + match = formatter.call(self, val); + + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // apply env-specific formatting (colors, etc.) + exports.formatArgs.call(self, args); + + var logFn = debug.log || exports.log || console.log.bind(console); + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.enabled = exports.enabled(namespace); + debug.useColors = exports.useColors(); + debug.color = selectColor(namespace); + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + return debug; +} + +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + +function enable(namespaces) { + exports.save(namespaces); + + exports.names = []; + exports.skips = []; + + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (var i = 0; i < len; i++) { + if (!split[i]) continue; // ignore empty strings + namespaces = split[i].replace(/\*/g, '.*?'); + if (namespaces[0] === '-') { + exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + exports.names.push(new RegExp('^' + namespaces + '$')); + } + } +} + +/** + * Disable debug output. + * + * @api public + */ + +function disable() { + exports.enable(''); +} + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; + } + } + return false; +} + +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + + +/***/ }), +/* 859 */ +/***/ (function(module, exports) { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isNaN(val) === false) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + if (ms >= d) { + return Math.round(ms / d) + 'd'; + } + if (ms >= h) { + return Math.round(ms / h) + 'h'; + } + if (ms >= m) { + return Math.round(ms / m) + 'm'; + } + if (ms >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, n, name) { + if (ms < n) { + return; + } + if (ms < n * 1.5) { + return Math.floor(ms / n) + ' ' + name; + } + return Math.ceil(ms / n) + ' ' + name + 's'; +} + + +/***/ }), +/* 860 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * Module dependencies. + */ + +var tty = __webpack_require__(480); +var util = __webpack_require__(29); + +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(858); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(function (key) { + return /^debug_/i.test(key); +}).reduce(function (obj, key) { + // camel-case + var prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); + + // coerce string value into JS value + var val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) val = true; + else if (/^(no|off|false|disabled)$/i.test(val)) val = false; + else if (val === 'null') val = null; + else val = Number(val); + + obj[prop] = val; + return obj; +}, {}); + +/** + * The file descriptor to write the `debug()` calls to. + * Set the `DEBUG_FD` env variable to override with another value. i.e.: + * + * $ DEBUG_FD=3 node script.js 3>debug.log + */ + +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; + +if (1 !== fd && 2 !== fd) { + util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() +} + +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +exports.formatters.o = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n').map(function(str) { + return str.trim() + }).join(' '); +}; + +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ + +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; + + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = new Date().toUTCString() + + ' ' + name + ' ' + args[0]; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ + +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + +function save(namespaces) { + if (null == namespaces) { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } else { + process.env.DEBUG = namespaces; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Copied from `node/src/node.js`. + * + * XXX: It's lame that node doesn't expose this API out-of-the-box. It also + * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. + */ + +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); + + // Note stream._type is used for test-module-load-list.js + + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; + + // Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + case 'FILE': + var fs = __webpack_require__(23); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; + + case 'PIPE': + case 'TCP': + var net = __webpack_require__(798); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); + + // FIXME Should probably have an option in net.Socket to create a + // stream from an existing fd which is writable only. But for now + // we'll just add this hack and set the `readable` member to false. + // Test: ./node test/fixtures/echo.js < /etc/passwd + stream.readable = false; + stream.read = null; + stream._type = 'pipe'; + + // FIXME Hack to have stream not keep the event loop alive. + // See https://github.com/joyent/node/issues/1726 + if (stream._handle && stream._handle.unref) { + stream._handle.unref(); + } + break; + + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } + + // For supporting legacy API we put the FD here. + stream.fd = fd; + + stream._isStdio = true; + + return stream; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init (debug) { + debug.inspectOpts = {}; + + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ + +exports.enable(load()); + + +/***/ }), +/* 861 */ +/***/ (function(module, exports, __webpack_require__) { + "use strict"; var brackets = __webpack_require__(851); -var define = __webpack_require__(857); -var utils = __webpack_require__(858); +var define = __webpack_require__(862); +var utils = __webpack_require__(863); /** * Characters to use in text regex (we want to "not" match @@ -100824,7 +101694,7 @@ module.exports = parsers; /***/ }), -/* 857 */ +/* 862 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100862,7 +101732,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 858 */ +/* 863 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100938,7 +101808,7 @@ utils.createRegex = function(str) { /***/ }), -/* 859 */ +/* 864 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100949,7 +101819,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(760); -var define = __webpack_require__(857); +var define = __webpack_require__(862); var extend = __webpack_require__(730); /** @@ -100957,7 +101827,7 @@ var extend = __webpack_require__(730); */ var compilers = __webpack_require__(850); -var parsers = __webpack_require__(856); +var parsers = __webpack_require__(861); /** * Customize Snapdragon parser and renderer @@ -101023,7 +101893,7 @@ module.exports = Extglob; /***/ }), -/* 860 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101113,14 +101983,14 @@ function textRegex(pattern) { /***/ }), -/* 861 */ +/* 866 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(842))(); /***/ }), -/* 862 */ +/* 867 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101138,7 +102008,7 @@ utils.define = __webpack_require__(829); utils.diff = __webpack_require__(846); utils.extend = __webpack_require__(830); utils.pick = __webpack_require__(847); -utils.typeOf = __webpack_require__(863); +utils.typeOf = __webpack_require__(868); utils.unique = __webpack_require__(733); /** @@ -101436,7 +102306,7 @@ utils.unixify = function(options) { /***/ }), -/* 863 */ +/* 868 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -101571,7 +102441,7 @@ function isBuffer(val) { /***/ }), -/* 864 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101590,9 +102460,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(865); -var reader_1 = __webpack_require__(878); -var fs_stream_1 = __webpack_require__(882); +var readdir = __webpack_require__(870); +var reader_1 = __webpack_require__(883); +var fs_stream_1 = __webpack_require__(887); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -101653,15 +102523,15 @@ exports.default = ReaderAsync; /***/ }), -/* 865 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(866); -const readdirAsync = __webpack_require__(874); -const readdirStream = __webpack_require__(877); +const readdirSync = __webpack_require__(871); +const readdirAsync = __webpack_require__(879); +const readdirStream = __webpack_require__(882); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -101745,7 +102615,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 866 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101753,11 +102623,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(867); +const DirectoryReader = __webpack_require__(872); let syncFacade = { - fs: __webpack_require__(872), - forEach: __webpack_require__(873), + fs: __webpack_require__(877), + forEach: __webpack_require__(878), sync: true }; @@ -101786,7 +102656,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 867 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101795,9 +102665,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__(868); -const stat = __webpack_require__(870); -const call = __webpack_require__(871); +const normalizeOptions = __webpack_require__(873); +const stat = __webpack_require__(875); +const call = __webpack_require__(876); /** * Asynchronously reads the contents of a directory and streams the results @@ -102173,14 +103043,14 @@ module.exports = DirectoryReader; /***/ }), -/* 868 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(869); +const globToRegExp = __webpack_require__(874); module.exports = normalizeOptions; @@ -102357,7 +103227,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 869 */ +/* 874 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -102494,13 +103364,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 870 */ +/* 875 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(871); +const call = __webpack_require__(876); module.exports = stat; @@ -102575,7 +103445,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 871 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102636,14 +103506,14 @@ function callOnce (fn) { /***/ }), -/* 872 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(871); +const call = __webpack_require__(876); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -102707,7 +103577,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 873 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102736,7 +103606,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 874 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102744,12 +103614,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(875); -const DirectoryReader = __webpack_require__(867); +const maybe = __webpack_require__(880); +const DirectoryReader = __webpack_require__(872); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(876), + forEach: __webpack_require__(881), async: true }; @@ -102791,7 +103661,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 875 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102818,7 +103688,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 876 */ +/* 881 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102854,7 +103724,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 877 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102862,11 +103732,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(867); +const DirectoryReader = __webpack_require__(872); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(876), + forEach: __webpack_require__(881), async: true }; @@ -102886,16 +103756,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 878 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(879); -var entry_1 = __webpack_require__(881); -var pathUtil = __webpack_require__(880); +var deep_1 = __webpack_require__(884); +var entry_1 = __webpack_require__(886); +var pathUtil = __webpack_require__(885); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -102961,13 +103831,13 @@ exports.default = Reader; /***/ }), -/* 879 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(880); +var pathUtils = __webpack_require__(885); var patternUtils = __webpack_require__(714); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -103051,7 +103921,7 @@ exports.default = DeepFilter; /***/ }), -/* 880 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103082,13 +103952,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 881 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(880); +var pathUtils = __webpack_require__(885); var patternUtils = __webpack_require__(714); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -103174,7 +104044,7 @@ exports.default = EntryFilter; /***/ }), -/* 882 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103194,8 +104064,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(883); -var fs_1 = __webpack_require__(887); +var fsStat = __webpack_require__(888); +var fs_1 = __webpack_require__(892); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -103245,14 +104115,14 @@ exports.default = FileSystemStream; /***/ }), -/* 883 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(884); -const statProvider = __webpack_require__(886); +const optionsManager = __webpack_require__(889); +const statProvider = __webpack_require__(891); /** * Asynchronous API. */ @@ -103283,13 +104153,13 @@ exports.statSync = statSync; /***/ }), -/* 884 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(885); +const fsAdapter = __webpack_require__(890); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -103302,7 +104172,7 @@ exports.prepare = prepare; /***/ }), -/* 885 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103325,7 +104195,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 886 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103377,7 +104247,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 887 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103408,7 +104278,7 @@ exports.default = FileSystem; /***/ }), -/* 888 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103428,9 +104298,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(865); -var reader_1 = __webpack_require__(878); -var fs_stream_1 = __webpack_require__(882); +var readdir = __webpack_require__(870); +var reader_1 = __webpack_require__(883); +var fs_stream_1 = __webpack_require__(887); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -103498,7 +104368,7 @@ exports.default = ReaderStream; /***/ }), -/* 889 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103517,9 +104387,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(865); -var reader_1 = __webpack_require__(878); -var fs_sync_1 = __webpack_require__(890); +var readdir = __webpack_require__(870); +var reader_1 = __webpack_require__(883); +var fs_sync_1 = __webpack_require__(895); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -103579,7 +104449,7 @@ exports.default = ReaderSync; /***/ }), -/* 890 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103598,8 +104468,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(883); -var fs_1 = __webpack_require__(887); +var fsStat = __webpack_require__(888); +var fs_1 = __webpack_require__(892); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -103645,7 +104515,7 @@ exports.default = FileSystemSync; /***/ }), -/* 891 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103661,7 +104531,7 @@ exports.flatten = flatten; /***/ }), -/* 892 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103682,13 +104552,13 @@ exports.merge = merge; /***/ }), -/* 893 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(894); +const pathType = __webpack_require__(899); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -103754,13 +104624,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 894 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(895); +const pify = __webpack_require__(900); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -103803,7 +104673,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 895 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103894,7 +104764,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 896 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103902,9 +104772,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(710); -const gitIgnore = __webpack_require__(897); -const pify = __webpack_require__(898); -const slash = __webpack_require__(899); +const gitIgnore = __webpack_require__(902); +const pify = __webpack_require__(903); +const slash = __webpack_require__(904); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -104002,7 +104872,7 @@ module.exports.sync = options => { /***/ }), -/* 897 */ +/* 902 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -104471,7 +105341,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 898 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104546,7 +105416,7 @@ module.exports = (input, options) => { /***/ }), -/* 899 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104564,17 +105434,17 @@ module.exports = input => { /***/ }), -/* 900 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(901); -const CpFileError = __webpack_require__(902); -const fs = __webpack_require__(904); -const ProgressEmitter = __webpack_require__(906); +const {Buffer} = __webpack_require__(906); +const CpFileError = __webpack_require__(907); +const fs = __webpack_require__(909); +const ProgressEmitter = __webpack_require__(911); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -104728,7 +105598,7 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 901 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ @@ -104796,12 +105666,12 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 902 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(903); +const NestedError = __webpack_require__(908); class CpFileError extends NestedError { constructor(message, nested) { @@ -104815,7 +105685,7 @@ module.exports = CpFileError; /***/ }), -/* 903 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(509); @@ -104869,15 +105739,15 @@ module.exports = NestedError; /***/ }), -/* 904 */ +/* 909 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(559); -const pify = __webpack_require__(905); -const CpFileError = __webpack_require__(902); +const pify = __webpack_require__(910); +const CpFileError = __webpack_require__(907); const fsP = pify(fs); @@ -105022,7 +105892,7 @@ if (fs.copyFileSync) { /***/ }), -/* 905 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105097,7 +105967,7 @@ module.exports = (input, options) => { /***/ }), -/* 906 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105138,12 +106008,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 907 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(908); +const NestedError = __webpack_require__(913); class CpyError extends NestedError { constructor(message, nested) { @@ -105157,7 +106027,7 @@ module.exports = CpyError; /***/ }), -/* 908 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -105213,7 +106083,7 @@ module.exports = NestedError; /***/ }), -/* 909 */ +/* 914 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/renovate.json5 b/renovate.json5 index ecc9b3b2ceb624..f069e961c0f2be 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -46,35 +46,6 @@ enabled: false, }, packageRules: [ - { - groupSlug: 'eslint', - groupName: 'eslint related packages', - packagePatterns: [ - '(\\b|_)eslint(\\b|_)', - ], - }, - { - groupSlug: 'babel', - groupName: 'babel related packages', - packagePatterns: [ - '(\\b|_)babel(\\b|_)', - ], - packageNames: [ - 'core-js', - '@types/core-js', - '@babel/preset-react', - '@types/babel__preset-react', - '@babel/preset-typescript', - '@types/babel__preset-typescript', - ], - }, - { - groupSlug: 'jest', - groupName: 'jest related packages', - packagePatterns: [ - '(\\b|_)jest(\\b|_)', - ], - }, { groupSlug: '@elastic/charts', groupName: '@elastic/charts related packages', @@ -88,31 +59,11 @@ masterIssueApproval: false, }, { - groupSlug: 'mocha', - groupName: 'mocha related packages', - packagePatterns: [ - '(\\b|_)mocha(\\b|_)', - ], - }, - { - groupSlug: 'karma', - groupName: 'karma related packages', - packagePatterns: [ - '(\\b|_)karma(\\b|_)', - ], - }, - { - groupSlug: 'gulp', - groupName: 'gulp related packages', - packagePatterns: [ - '(\\b|_)gulp(\\b|_)', - ], - }, - { - groupSlug: 'grunt', - groupName: 'grunt related packages', - packagePatterns: [ - '(\\b|_)grunt(\\b|_)', + groupSlug: '@reach/router', + groupName: '@reach/router related packages', + packageNames: [ + '@reach/router', + '@types/reach__router', ], }, { @@ -122,126 +73,6 @@ '(\\b|_)angular(\\b|_)', ], }, - { - groupSlug: 'd3', - groupName: 'd3 related packages', - packagePatterns: [ - '(\\b|_)d3(\\b|_)', - ], - }, - { - groupSlug: 'react', - groupName: 'react related packages', - packagePatterns: [ - '(\\b|_)react(\\b|_)', - '(\\b|_)redux(\\b|_)', - '(\\b|_)enzyme(\\b|_)', - ], - packageNames: [ - 'ngreact', - '@types/ngreact', - 'recompose', - '@types/recompose', - 'prop-types', - '@types/prop-types', - 'typescript-fsa-reducers', - '@types/typescript-fsa-reducers', - 'reselect', - '@types/reselect', - ], - }, - { - groupSlug: 'moment', - groupName: 'moment related packages', - packagePatterns: [ - '(\\b|_)moment(\\b|_)', - ], - }, - { - groupSlug: 'graphql', - groupName: 'graphql related packages', - packagePatterns: [ - '(\\b|_)graphql(\\b|_)', - '(\\b|_)apollo(\\b|_)', - ], - }, - { - groupSlug: 'webpack', - groupName: 'webpack related packages', - packagePatterns: [ - '(\\b|_)webpack(\\b|_)', - '(\\b|_)loader(\\b|_)', - '(\\b|_)acorn(\\b|_)', - '(\\b|_)terser(\\b|_)', - ], - packageNames: [ - 'mini-css-extract-plugin', - '@types/mini-css-extract-plugin', - 'chokidar', - '@types/chokidar', - ], - }, - { - groupSlug: 'vega', - groupName: 'vega related packages', - packagePatterns: [ - '(\\b|_)vega(\\b|_)', - ], - enabled: false, - }, - { - groupSlug: 'language server', - groupName: 'language server related packages', - packageNames: [ - 'vscode-jsonrpc', - '@types/vscode-jsonrpc', - 'vscode-languageserver', - '@types/vscode-languageserver', - 'vscode-languageserver-types', - '@types/vscode-languageserver-types', - ], - }, - { - groupSlug: 'hapi', - groupName: 'hapi related packages', - packagePatterns: [ - '(\\b|_)hapi(\\b|_)', - ], - packageNames: [ - 'hapi', - '@types/hapi', - 'joi', - '@types/joi', - 'boom', - '@types/boom', - 'hoek', - '@types/hoek', - 'h2o2', - '@types/h2o2', - '@elastic/good', - '@types/elastic__good', - 'good-squeeze', - '@types/good-squeeze', - 'inert', - '@types/inert', - ], - }, - { - groupSlug: 'dragselect', - groupName: 'dragselect related packages', - packageNames: [ - 'dragselect', - '@types/dragselect', - ], - labels: [ - 'release_note:skip', - 'Team:Operations', - 'renovate', - 'v8.0.0', - 'v7.6.0', - ':ml', - ], - }, { groupSlug: 'api-documenter', groupName: 'api-documenter related packages', @@ -254,47 +85,34 @@ enabled: false, }, { - groupSlug: 'jsts', - groupName: 'jsts related packages', + groupSlug: 'archiver', + groupName: 'archiver related packages', packageNames: [ - 'jsts', - '@types/jsts', - ], - allowedVersions: '^1.6.2', - }, - { - groupSlug: 'storybook', - groupName: 'storybook related packages', - packagePatterns: [ - '(\\b|_)storybook(\\b|_)', + 'archiver', + '@types/archiver', ], }, { - groupSlug: 'typescript', - groupName: 'typescript related packages', + groupSlug: 'babel', + groupName: 'babel related packages', packagePatterns: [ - '(\\b|_)ts(\\b|_)', - '(\\b|_)typescript(\\b|_)', + '(\\b|_)babel(\\b|_)', ], packageNames: [ - 'tslib', - '@types/tslib', - ], - }, - { - groupSlug: 'json-stable-stringify', - groupName: 'json-stable-stringify related packages', - packageNames: [ - 'json-stable-stringify', - '@types/json-stable-stringify', + 'core-js', + '@types/core-js', + '@babel/preset-react', + '@types/babel__preset-react', + '@babel/preset-typescript', + '@types/babel__preset-typescript', ], }, { - groupSlug: 'lodash.clonedeep', - groupName: 'lodash.clonedeep related packages', + groupSlug: 'base64-js', + groupName: 'base64-js related packages', packageNames: [ - 'lodash.clonedeep', - '@types/lodash.clonedeep', + 'base64-js', + '@types/base64-js', ], }, { @@ -321,6 +139,14 @@ '@types/cheerio', ], }, + { + groupSlug: 'chroma-js', + groupName: 'chroma-js related packages', + packageNames: [ + 'chroma-js', + '@types/chroma-js', + ], + }, { groupSlug: 'chromedriver', groupName: 'chromedriver related packages', @@ -338,403 +164,441 @@ ], }, { - groupSlug: 'dedent', - groupName: 'dedent related packages', + groupSlug: 'cmd-shim', + groupName: 'cmd-shim related packages', packageNames: [ - 'dedent', - '@types/dedent', + 'cmd-shim', + '@types/cmd-shim', ], }, { - groupSlug: 'delete-empty', - groupName: 'delete-empty related packages', + groupSlug: 'color', + groupName: 'color related packages', packageNames: [ - 'delete-empty', - '@types/delete-empty', + 'color', + '@types/color', ], }, { - groupSlug: 'elasticsearch', - groupName: 'elasticsearch related packages', + groupSlug: 'cpy', + groupName: 'cpy related packages', packageNames: [ - 'elasticsearch', - '@types/elasticsearch', + 'cpy', + '@types/cpy', ], }, { - groupSlug: 'fetch-mock', - groupName: 'fetch-mock related packages', + groupSlug: 'cytoscape', + groupName: 'cytoscape related packages', packageNames: [ - 'fetch-mock', - '@types/fetch-mock', + 'cytoscape', + '@types/cytoscape', ], }, { - groupSlug: 'getopts', - groupName: 'getopts related packages', - packageNames: [ - 'getopts', - '@types/getopts', + groupSlug: 'd3', + groupName: 'd3 related packages', + packagePatterns: [ + '(\\b|_)d3(\\b|_)', ], }, { - groupSlug: 'glob', - groupName: 'glob related packages', + groupSlug: 'dedent', + groupName: 'dedent related packages', packageNames: [ - 'glob', - '@types/glob', + 'dedent', + '@types/dedent', ], }, { - groupSlug: 'globby', - groupName: 'globby related packages', + groupSlug: 'delete-empty', + groupName: 'delete-empty related packages', packageNames: [ - 'globby', - '@types/globby', + 'delete-empty', + '@types/delete-empty', ], }, { - groupSlug: 'has-ansi', - groupName: 'has-ansi related packages', + groupSlug: 'dragselect', + groupName: 'dragselect related packages', packageNames: [ - 'has-ansi', - '@types/has-ansi', + 'dragselect', + '@types/dragselect', + ], + labels: [ + 'release_note:skip', + 'Team:Operations', + 'renovate', + 'v8.0.0', + 'v7.6.0', + ':ml', ], }, { - groupSlug: 'history', - groupName: 'history related packages', + groupSlug: 'elasticsearch', + groupName: 'elasticsearch related packages', packageNames: [ - 'history', - '@types/history', + 'elasticsearch', + '@types/elasticsearch', ], }, { - groupSlug: 'jquery', - groupName: 'jquery related packages', - packageNames: [ - 'jquery', - '@types/jquery', + groupSlug: 'eslint', + groupName: 'eslint related packages', + packagePatterns: [ + '(\\b|_)eslint(\\b|_)', ], }, { - groupSlug: 'js-yaml', - groupName: 'js-yaml related packages', + groupSlug: 'fancy-log', + groupName: 'fancy-log related packages', packageNames: [ - 'js-yaml', - '@types/js-yaml', + 'fancy-log', + '@types/fancy-log', ], }, { - groupSlug: 'json5', - groupName: 'json5 related packages', + groupSlug: 'fetch-mock', + groupName: 'fetch-mock related packages', packageNames: [ - 'json5', - '@types/json5', + 'fetch-mock', + '@types/fetch-mock', ], }, { - groupSlug: 'license-checker', - groupName: 'license-checker related packages', + groupSlug: 'file-saver', + groupName: 'file-saver related packages', packageNames: [ - 'license-checker', - '@types/license-checker', + 'file-saver', + '@types/file-saver', ], }, { - groupSlug: 'listr', - groupName: 'listr related packages', + groupSlug: 'getopts', + groupName: 'getopts related packages', packageNames: [ - 'listr', - '@types/listr', + 'getopts', + '@types/getopts', ], }, { - groupSlug: 'lodash', - groupName: 'lodash related packages', + groupSlug: 'getos', + groupName: 'getos related packages', packageNames: [ - 'lodash', - '@types/lodash', + 'getos', + '@types/getos', ], }, { - groupSlug: 'lru-cache', - groupName: 'lru-cache related packages', + groupSlug: 'git-url-parse', + groupName: 'git-url-parse related packages', packageNames: [ - 'lru-cache', - '@types/lru-cache', + 'git-url-parse', + '@types/git-url-parse', ], }, { - groupSlug: 'markdown-it', - groupName: 'markdown-it related packages', + groupSlug: 'glob', + groupName: 'glob related packages', packageNames: [ - 'markdown-it', - '@types/markdown-it', + 'glob', + '@types/glob', ], }, { - groupSlug: 'minimatch', - groupName: 'minimatch related packages', + groupSlug: 'globby', + groupName: 'globby related packages', packageNames: [ - 'minimatch', - '@types/minimatch', + 'globby', + '@types/globby', ], }, { - groupSlug: 'mustache', - groupName: 'mustache related packages', - packageNames: [ - 'mustache', - '@types/mustache', + groupSlug: 'graphql', + groupName: 'graphql related packages', + packagePatterns: [ + '(\\b|_)graphql(\\b|_)', + '(\\b|_)apollo(\\b|_)', ], }, { - groupSlug: 'node', - groupName: 'node related packages', - packageNames: [ - 'node', - '@types/node', + groupSlug: 'grunt', + groupName: 'grunt related packages', + packagePatterns: [ + '(\\b|_)grunt(\\b|_)', ], }, { - groupSlug: 'opn', - groupName: 'opn related packages', + groupSlug: 'gulp', + groupName: 'gulp related packages', + packagePatterns: [ + '(\\b|_)gulp(\\b|_)', + ], + }, + { + groupSlug: 'hapi', + groupName: 'hapi related packages', + packagePatterns: [ + '(\\b|_)hapi(\\b|_)', + ], packageNames: [ - 'opn', - '@types/opn', + 'hapi', + '@types/hapi', + 'joi', + '@types/joi', + 'boom', + '@types/boom', + 'hoek', + '@types/hoek', + 'h2o2', + '@types/h2o2', + '@elastic/good', + '@types/elastic__good', + 'good-squeeze', + '@types/good-squeeze', + 'inert', + '@types/inert', ], }, { - groupSlug: 'pngjs', - groupName: 'pngjs related packages', + groupSlug: 'has-ansi', + groupName: 'has-ansi related packages', packageNames: [ - 'pngjs', - '@types/pngjs', + 'has-ansi', + '@types/has-ansi', ], }, { - groupSlug: 'podium', - groupName: 'podium related packages', + groupSlug: 'history', + groupName: 'history related packages', packageNames: [ - 'podium', - '@types/podium', + 'history', + '@types/history', ], }, { - groupSlug: '@reach/router', - groupName: '@reach/router related packages', + groupSlug: 'indent-string', + groupName: 'indent-string related packages', packageNames: [ - '@reach/router', - '@types/reach__router', + 'indent-string', + '@types/indent-string', ], }, { - groupSlug: 'request', - groupName: 'request related packages', + groupSlug: 'intl-relativeformat', + groupName: 'intl-relativeformat related packages', packageNames: [ - 'request', - '@types/request', + 'intl-relativeformat', + '@types/intl-relativeformat', ], }, { - groupSlug: 'selenium-webdriver', - groupName: 'selenium-webdriver related packages', - packageNames: [ - 'selenium-webdriver', - '@types/selenium-webdriver', + groupSlug: 'jest', + groupName: 'jest related packages', + packagePatterns: [ + '(\\b|_)jest(\\b|_)', ], }, { - groupSlug: 'semver', - groupName: 'semver related packages', + groupSlug: 'jquery', + groupName: 'jquery related packages', packageNames: [ - 'semver', - '@types/semver', + 'jquery', + '@types/jquery', ], }, { - groupSlug: 'sinon', - groupName: 'sinon related packages', + groupSlug: 'js-yaml', + groupName: 'js-yaml related packages', packageNames: [ - 'sinon', - '@types/sinon', + 'js-yaml', + '@types/js-yaml', ], }, { - groupSlug: 'strip-ansi', - groupName: 'strip-ansi related packages', + groupSlug: 'jsdom', + groupName: 'jsdom related packages', packageNames: [ - 'strip-ansi', - '@types/strip-ansi', + 'jsdom', + '@types/jsdom', ], }, { - groupSlug: 'styled-components', - groupName: 'styled-components related packages', + groupSlug: 'json-stable-stringify', + groupName: 'json-stable-stringify related packages', packageNames: [ - 'styled-components', - '@types/styled-components', + 'json-stable-stringify', + '@types/json-stable-stringify', ], }, { - groupSlug: 'supertest', - groupName: 'supertest related packages', + groupSlug: 'json5', + groupName: 'json5 related packages', packageNames: [ - 'supertest', - '@types/supertest', + 'json5', + '@types/json5', ], }, { - groupSlug: 'supertest-as-promised', - groupName: 'supertest-as-promised related packages', + groupSlug: 'jsonwebtoken', + groupName: 'jsonwebtoken related packages', packageNames: [ - 'supertest-as-promised', - '@types/supertest-as-promised', + 'jsonwebtoken', + '@types/jsonwebtoken', ], }, { - groupSlug: 'type-detect', - groupName: 'type-detect related packages', + groupSlug: 'jsts', + groupName: 'jsts related packages', packageNames: [ - 'type-detect', - '@types/type-detect', + 'jsts', + '@types/jsts', ], + allowedVersions: '^1.6.2', }, { - groupSlug: 'uuid', - groupName: 'uuid related packages', - packageNames: [ - 'uuid', - '@types/uuid', + groupSlug: 'karma', + groupName: 'karma related packages', + packagePatterns: [ + '(\\b|_)karma(\\b|_)', ], }, { - groupSlug: 'vinyl-fs', - groupName: 'vinyl-fs related packages', + groupSlug: 'language server', + groupName: 'language server related packages', packageNames: [ - 'vinyl-fs', - '@types/vinyl-fs', + 'vscode-jsonrpc', + '@types/vscode-jsonrpc', + 'vscode-languageserver', + '@types/vscode-languageserver', + 'vscode-languageserver-types', + '@types/vscode-languageserver-types', ], }, { - groupSlug: 'zen-observable', - groupName: 'zen-observable related packages', + groupSlug: 'license-checker', + groupName: 'license-checker related packages', packageNames: [ - 'zen-observable', - '@types/zen-observable', + 'license-checker', + '@types/license-checker', ], }, { - groupSlug: 'archiver', - groupName: 'archiver related packages', + groupSlug: 'listr', + groupName: 'listr related packages', packageNames: [ - 'archiver', - '@types/archiver', + 'listr', + '@types/listr', ], }, { - groupSlug: 'base64-js', - groupName: 'base64-js related packages', + groupSlug: 'lodash', + groupName: 'lodash related packages', packageNames: [ - 'base64-js', - '@types/base64-js', + 'lodash', + '@types/lodash', ], }, { - groupSlug: 'chroma-js', - groupName: 'chroma-js related packages', + groupSlug: 'lodash.clonedeep', + groupName: 'lodash.clonedeep related packages', packageNames: [ - 'chroma-js', - '@types/chroma-js', + 'lodash.clonedeep', + '@types/lodash.clonedeep', ], }, { - groupSlug: 'color', - groupName: 'color related packages', + groupSlug: 'lodash.clonedeepwith', + groupName: 'lodash.clonedeepwith related packages', packageNames: [ - 'color', - '@types/color', + 'lodash.clonedeepwith', + '@types/lodash.clonedeepwith', ], }, { - groupSlug: 'cytoscape', - groupName: 'cytoscape related packages', + groupSlug: 'log-symbols', + groupName: 'log-symbols related packages', packageNames: [ - 'cytoscape', - '@types/cytoscape', + 'log-symbols', + '@types/log-symbols', ], }, { - groupSlug: 'fancy-log', - groupName: 'fancy-log related packages', + groupSlug: 'lru-cache', + groupName: 'lru-cache related packages', packageNames: [ - 'fancy-log', - '@types/fancy-log', + 'lru-cache', + '@types/lru-cache', ], }, { - groupSlug: 'file-saver', - groupName: 'file-saver related packages', + groupSlug: 'mapbox-gl', + groupName: 'mapbox-gl related packages', packageNames: [ - 'file-saver', - '@types/file-saver', + 'mapbox-gl', + '@types/mapbox-gl', ], }, { - groupSlug: 'getos', - groupName: 'getos related packages', + groupSlug: 'markdown-it', + groupName: 'markdown-it related packages', packageNames: [ - 'getos', - '@types/getos', + 'markdown-it', + '@types/markdown-it', ], }, { - groupSlug: 'git-url-parse', - groupName: 'git-url-parse related packages', + groupSlug: 'memoize-one', + groupName: 'memoize-one related packages', packageNames: [ - 'git-url-parse', - '@types/git-url-parse', + 'memoize-one', + '@types/memoize-one', ], }, { - groupSlug: 'jsdom', - groupName: 'jsdom related packages', + groupSlug: 'mime', + groupName: 'mime related packages', packageNames: [ - 'jsdom', - '@types/jsdom', + 'mime', + '@types/mime', ], }, { - groupSlug: 'jsonwebtoken', - groupName: 'jsonwebtoken related packages', + groupSlug: 'minimatch', + groupName: 'minimatch related packages', packageNames: [ - 'jsonwebtoken', - '@types/jsonwebtoken', + 'minimatch', + '@types/minimatch', ], }, { - groupSlug: 'mapbox-gl', - groupName: 'mapbox-gl related packages', - packageNames: [ - 'mapbox-gl', - '@types/mapbox-gl', + groupSlug: 'mocha', + groupName: 'mocha related packages', + packagePatterns: [ + '(\\b|_)mocha(\\b|_)', ], }, { - groupSlug: 'memoize-one', - groupName: 'memoize-one related packages', + groupSlug: 'moment', + groupName: 'moment related packages', + packagePatterns: [ + '(\\b|_)moment(\\b|_)', + ], + }, + { + groupSlug: 'mustache', + groupName: 'mustache related packages', packageNames: [ - 'memoize-one', - '@types/memoize-one', + 'mustache', + '@types/mustache', ], }, { - groupSlug: 'mime', - groupName: 'mime related packages', + groupSlug: 'ncp', + groupName: 'ncp related packages', packageNames: [ - 'mime', - '@types/mime', + 'ncp', + '@types/ncp', ], }, { @@ -745,6 +609,14 @@ '@types/nock', ], }, + { + groupSlug: 'node', + groupName: 'node related packages', + packageNames: [ + 'node', + '@types/node', + ], + }, { groupSlug: 'node-fetch', groupName: 'node-fetch related packages', @@ -769,6 +641,22 @@ '@types/object-hash', ], }, + { + groupSlug: 'opn', + groupName: 'opn related packages', + packageNames: [ + 'opn', + '@types/opn', + ], + }, + { + groupSlug: 'ora', + groupName: 'ora related packages', + packageNames: [ + 'ora', + '@types/ora', + ], + }, { groupSlug: 'papaparse', groupName: 'papaparse related packages', @@ -777,6 +665,30 @@ '@types/papaparse', ], }, + { + groupSlug: 'parse-link-header', + groupName: 'parse-link-header related packages', + packageNames: [ + 'parse-link-header', + '@types/parse-link-header', + ], + }, + { + groupSlug: 'pngjs', + groupName: 'pngjs related packages', + packageNames: [ + 'pngjs', + '@types/pngjs', + ], + }, + { + groupSlug: 'podium', + groupName: 'podium related packages', + packageNames: [ + 'podium', + '@types/podium', + ], + }, { groupSlug: 'proper-lockfile', groupName: 'proper-lockfile related packages', @@ -793,6 +705,35 @@ '@types/puppeteer', ], }, + { + groupSlug: 'react', + groupName: 'react related packages', + packagePatterns: [ + '(\\b|_)react(\\b|_)', + '(\\b|_)redux(\\b|_)', + '(\\b|_)enzyme(\\b|_)', + ], + packageNames: [ + 'ngreact', + '@types/ngreact', + 'recompose', + '@types/recompose', + 'prop-types', + '@types/prop-types', + 'typescript-fsa-reducers', + '@types/typescript-fsa-reducers', + 'reselect', + '@types/reselect', + ], + }, + { + groupSlug: 'read-pkg', + groupName: 'read-pkg related packages', + packageNames: [ + 'read-pkg', + '@types/read-pkg', + ], + }, { groupSlug: 'reduce-reducers', groupName: 'reduce-reducers related packages', @@ -802,123 +743,166 @@ ], }, { - groupSlug: 'tar-fs', - groupName: 'tar-fs related packages', + groupSlug: 'request', + groupName: 'request related packages', packageNames: [ - 'tar-fs', - '@types/tar-fs', + 'request', + '@types/request', ], }, { - groupSlug: 'tinycolor2', - groupName: 'tinycolor2 related packages', + groupSlug: 'selenium-webdriver', + groupName: 'selenium-webdriver related packages', packageNames: [ - 'tinycolor2', - '@types/tinycolor2', + 'selenium-webdriver', + '@types/selenium-webdriver', ], }, { - groupSlug: 'xml-crypto', - groupName: 'xml-crypto related packages', + groupSlug: 'semver', + groupName: 'semver related packages', packageNames: [ - 'xml-crypto', - '@types/xml-crypto', + 'semver', + '@types/semver', ], }, { - groupSlug: 'xml2js', - groupName: 'xml2js related packages', + groupSlug: 'sinon', + groupName: 'sinon related packages', packageNames: [ - 'xml2js', - '@types/xml2js', + 'sinon', + '@types/sinon', ], }, { - groupSlug: 'intl-relativeformat', - groupName: 'intl-relativeformat related packages', + groupSlug: 'storybook', + groupName: 'storybook related packages', + packagePatterns: [ + '(\\b|_)storybook(\\b|_)', + ], + }, + { + groupSlug: 'strip-ansi', + groupName: 'strip-ansi related packages', packageNames: [ - 'intl-relativeformat', - '@types/intl-relativeformat', + 'strip-ansi', + '@types/strip-ansi', ], }, { - groupSlug: 'cmd-shim', - groupName: 'cmd-shim related packages', + groupSlug: 'strong-log-transformer', + groupName: 'strong-log-transformer related packages', packageNames: [ - 'cmd-shim', - '@types/cmd-shim', + 'strong-log-transformer', + '@types/strong-log-transformer', ], }, { - groupSlug: 'cpy', - groupName: 'cpy related packages', + groupSlug: 'styled-components', + groupName: 'styled-components related packages', packageNames: [ - 'cpy', - '@types/cpy', + 'styled-components', + '@types/styled-components', ], }, { - groupSlug: 'indent-string', - groupName: 'indent-string related packages', + groupSlug: 'supertest', + groupName: 'supertest related packages', packageNames: [ - 'indent-string', - '@types/indent-string', + 'supertest', + '@types/supertest', ], }, { - groupSlug: 'lodash.clonedeepwith', - groupName: 'lodash.clonedeepwith related packages', + groupSlug: 'supertest-as-promised', + groupName: 'supertest-as-promised related packages', packageNames: [ - 'lodash.clonedeepwith', - '@types/lodash.clonedeepwith', + 'supertest-as-promised', + '@types/supertest-as-promised', ], }, { - groupSlug: 'log-symbols', - groupName: 'log-symbols related packages', + groupSlug: 'tar-fs', + groupName: 'tar-fs related packages', packageNames: [ - 'log-symbols', - '@types/log-symbols', + 'tar-fs', + '@types/tar-fs', ], }, { - groupSlug: 'ncp', - groupName: 'ncp related packages', + groupSlug: 'tempy', + groupName: 'tempy related packages', packageNames: [ - 'ncp', - '@types/ncp', + 'tempy', + '@types/tempy', ], }, { - groupSlug: 'ora', - groupName: 'ora related packages', + groupSlug: 'tinycolor2', + groupName: 'tinycolor2 related packages', packageNames: [ - 'ora', - '@types/ora', + 'tinycolor2', + '@types/tinycolor2', ], }, { - groupSlug: 'read-pkg', - groupName: 'read-pkg related packages', + groupSlug: 'type-detect', + groupName: 'type-detect related packages', packageNames: [ - 'read-pkg', - '@types/read-pkg', + 'type-detect', + '@types/type-detect', ], }, { - groupSlug: 'strong-log-transformer', - groupName: 'strong-log-transformer related packages', + groupSlug: 'typescript', + groupName: 'typescript related packages', + packagePatterns: [ + '(\\b|_)ts(\\b|_)', + '(\\b|_)typescript(\\b|_)', + ], packageNames: [ - 'strong-log-transformer', - '@types/strong-log-transformer', + 'tslib', + '@types/tslib', ], }, { - groupSlug: 'tempy', - groupName: 'tempy related packages', + groupSlug: 'uuid', + groupName: 'uuid related packages', packageNames: [ - 'tempy', - '@types/tempy', + 'uuid', + '@types/uuid', + ], + }, + { + groupSlug: 'vega', + groupName: 'vega related packages', + packagePatterns: [ + '(\\b|_)vega(\\b|_)', + ], + enabled: false, + }, + { + groupSlug: 'vinyl-fs', + groupName: 'vinyl-fs related packages', + packageNames: [ + 'vinyl-fs', + '@types/vinyl-fs', + ], + }, + { + groupSlug: 'webpack', + groupName: 'webpack related packages', + packagePatterns: [ + '(\\b|_)webpack(\\b|_)', + '(\\b|_)loader(\\b|_)', + '(\\b|_)acorn(\\b|_)', + '(\\b|_)terser(\\b|_)', + ], + packageNames: [ + 'mini-css-extract-plugin', + '@types/mini-css-extract-plugin', + 'chokidar', + '@types/chokidar', ], }, { @@ -938,11 +922,27 @@ ], }, { - groupSlug: 'parse-link-header', - groupName: 'parse-link-header related packages', + groupSlug: 'xml-crypto', + groupName: 'xml-crypto related packages', packageNames: [ - 'parse-link-header', - '@types/parse-link-header', + 'xml-crypto', + '@types/xml-crypto', + ], + }, + { + groupSlug: 'xml2js', + groupName: 'xml2js related packages', + packageNames: [ + 'xml2js', + '@types/xml2js', + ], + }, + { + groupSlug: 'zen-observable', + groupName: 'zen-observable related packages', + packageNames: [ + 'zen-observable', + '@types/zen-observable', ], }, { diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index 4472891e580fb9..fc88f2657018f0 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -17,12 +17,23 @@ * under the License. */ -require('../src/setup_node_env'); -require('@kbn/test').runTestsCli([ +// eslint-disable-next-line no-restricted-syntax +const alwaysImportedTests = [ require.resolve('../test/functional/config.js'), - require.resolve('../test/api_integration/config.js'), require.resolve('../test/plugin_functional/config.js'), - require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), +]; +// eslint-disable-next-line no-restricted-syntax +const onlyNotInCoverageTests = [ + require.resolve('../test/api_integration/config.js'), + require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/examples/config.js'), +]; + +require('../src/setup_node_env'); +require('@kbn/test').runTestsCli([ + // eslint-disable-next-line no-restricted-syntax + ...alwaysImportedTests, + // eslint-disable-next-line no-restricted-syntax + ...(!!process.env.CODE_COVERAGE ? [] : onlyNotInCoverageTests), ]); diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 1c78de966c46f7..b70ac610f24a79 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1194,6 +1194,7 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`context.elasticsearch.dataClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | | `server.plugins.elasticsearch.getCluster('admin')` | [`context.elasticsearch.adminClient`](/docs/development/core/server/kibana-plugin-server.iscopedclusterclient.md) | | +| `server.plugins.elasticsearch.createCluster(...)` | [`core.elasticsearch.createClient`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.createclient.md) | | | `server.savedObjects.setScopedSavedObjectsClientFactory` | [`core.savedObjects.setClientFactory`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | | | `server.savedObjects.addScopedSavedObjectsClientWrapperFactory` | [`core.savedObjects.addClientWrapper`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | | | `server.savedObjects.getSavedObjectsRepository` | [`core.savedObjects.createInternalRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) [`core.savedObjects.createScopedRepository`](/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | | diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/cluster_client.ts index d43ab9d546ed2e..2352677b8d3e0a 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/cluster_client.ts @@ -89,15 +89,35 @@ export interface FakeRequest { } /** - * Represents an Elasticsearch cluster API client and allows to call API on behalf - * of the internal Kibana user and the actual user that is derived from the request - * headers (via `asScoped(...)`). + * Represents an Elasticsearch cluster API client created by the platform. + * It allows to call API on behalf of the internal Kibana user and + * the actual user that is derived from the request headers (via `asScoped(...)`). * * See {@link ClusterClient}. * * @public */ -export type IClusterClient = Pick; +export type IClusterClient = Pick; + +/** + * Represents an Elasticsearch cluster API client created by a plugin. + * It allows to call API on behalf of the internal Kibana user and + * the actual user that is derived from the request headers (via `asScoped(...)`). + * + * See {@link ClusterClient}. + * + * @public + */ +export type ICustomClusterClient = Pick; + +/** + A user credentials container. + * It accommodates the necessary auth credentials to impersonate the current user. + * + * @public + * See {@link KibanaRequest}. + */ +export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; /** * {@inheritDoc IClusterClient} @@ -174,7 +194,7 @@ export class ClusterClient implements IClusterClient { * @param request - Request the `IScopedClusterClient` instance will be scoped to. * Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform */ - public asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient { + public asScoped(request?: ScopeableRequest): IScopedClusterClient { // It'd have been quite expensive to create and configure client for every incoming // request since it involves parsing of the config, reading of the SSL certificate and // key files etc. Moreover scoped client needs two Elasticsearch JS clients at the same diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index d935d1a66eccfd..1b52f22c4da09a 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -18,33 +18,67 @@ */ import { BehaviorSubject } from 'rxjs'; -import { IClusterClient } from './cluster_client'; +import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { IScopedClusterClient } from './scoped_cluster_client'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; -import { InternalElasticsearchServiceSetup } from './types'; +import { InternalElasticsearchServiceSetup, ElasticsearchServiceSetup } from './types'; const createScopedClusterClientMock = (): jest.Mocked => ({ callAsInternalUser: jest.fn(), callAsCurrentUser: jest.fn(), }); -const createClusterClientMock = (): jest.Mocked => ({ - callAsInternalUser: jest.fn(), - asScoped: jest.fn().mockImplementation(createScopedClusterClientMock), +const createCustomClusterClientMock = (): jest.Mocked => ({ + ...createClusterClientMock(), close: jest.fn(), }); +function createClusterClientMock() { + const client: jest.Mocked = { + callAsInternalUser: jest.fn(), + asScoped: jest.fn(), + }; + client.asScoped.mockReturnValue(createScopedClusterClientMock()); + return client; +} + +type MockedElasticSearchServiceSetup = jest.Mocked< + ElasticsearchServiceSetup & { + adminClient: jest.Mocked; + dataClient: jest.Mocked; + } +>; + const createSetupContractMock = () => { - const setupContract: jest.Mocked = { + const setupContract: MockedElasticSearchServiceSetup = { + createClient: jest.fn(), + adminClient: createClusterClientMock(), + dataClient: createClusterClientMock(), + }; + setupContract.createClient.mockReturnValue(createCustomClusterClientMock()); + setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + return setupContract; +}; + +type MockedInternalElasticSearchServiceSetup = jest.Mocked< + InternalElasticsearchServiceSetup & { + adminClient: jest.Mocked; + dataClient: jest.Mocked; + } +>; +const createInternalSetupContractMock = () => { + const setupContract: MockedInternalElasticSearchServiceSetup = { + ...createSetupContractMock(), legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), }, - - createClient: jest.fn().mockImplementation(createClusterClientMock), - adminClient$: new BehaviorSubject((createClusterClientMock() as unknown) as IClusterClient), - dataClient$: new BehaviorSubject((createClusterClientMock() as unknown) as IClusterClient), + adminClient$: new BehaviorSubject(createClusterClientMock()), + dataClient$: new BehaviorSubject(createClusterClientMock()), }; + setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); return setupContract; }; @@ -55,14 +89,16 @@ const createMock = () => { start: jest.fn(), stop: jest.fn(), }; - mocked.setup.mockResolvedValue(createSetupContractMock()); + mocked.setup.mockResolvedValue(createInternalSetupContractMock()); mocked.stop.mockResolvedValue(); return mocked; }; export const elasticsearchServiceMock = { create: createMock, - createSetupContract: createSetupContractMock, + createInternalSetup: createInternalSetupContractMock, + createSetup: createSetupContractMock, createClusterClient: createClusterClientMock, + createCustomClusterClient: createCustomClusterClientMock, createScopedClusterClient: createScopedClusterClientMock, }; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 6c4a1f263bc713..aefc8b1fdf047e 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -30,6 +30,7 @@ import { loggingServiceMock } from '../logging/logging_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; +import { elasticsearchServiceMock } from './elasticsearch_service.mock'; let elasticsearchService: ElasticsearchService; const configService = configServiceMock.create(); @@ -69,6 +70,27 @@ describe('#setup', () => { ); }); + it('returns data and admin client as a part of the contract', async () => { + const mockAdminClusterClientInstance = elasticsearchServiceMock.createClusterClient(); + const mockDataClusterClientInstance = elasticsearchServiceMock.createClusterClient(); + MockClusterClient.mockImplementationOnce( + () => mockAdminClusterClientInstance + ).mockImplementationOnce(() => mockDataClusterClientInstance); + + const setupContract = await elasticsearchService.setup(deps); + + const adminClient = setupContract.adminClient; + const dataClient = setupContract.dataClient; + + expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await adminClient.callAsInternalUser('any'); + expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + + expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await dataClient.callAsInternalUser('any'); + expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); + }); + it('returns data and admin client observables as a part of the contract', async () => { const mockAdminClusterClientInstance = { close: jest.fn() }; const mockDataClusterClientInstance = { close: jest.fn() }; diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index be0a817c54146c..de32e7f6cf2258 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -18,17 +18,18 @@ */ import { ConnectableObservable, Observable, Subscription } from 'rxjs'; -import { filter, first, map, publishReplay, switchMap } from 'rxjs/operators'; +import { filter, first, map, publishReplay, switchMap, take } from 'rxjs/operators'; import { CoreService } from '../../types'; import { merge } from '../../utils'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { ClusterClient } from './cluster_client'; +import { ClusterClient, ScopeableRequest } from './cluster_client'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; import { InternalElasticsearchServiceSetup } from './types'; +import { CallAPIOptions } from './api_types'; /** @internal */ interface CoreClusterClients { @@ -94,11 +95,67 @@ export class ElasticsearchService implements CoreService clients.adminClient)); + const dataClient$ = clients$.pipe(map(clients => clients.dataClient)); + + const adminClient = { + async callAsInternalUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await adminClient$.pipe(take(1)).toPromise(); + return await client.callAsInternalUser(endpoint, clientParams, options); + }, + asScoped(request: ScopeableRequest) { + return { + callAsInternalUser: adminClient.callAsInternalUser, + async callAsCurrentUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await adminClient$.pipe(take(1)).toPromise(); + return await client + .asScoped(request) + .callAsCurrentUser(endpoint, clientParams, options); + }, + }; + }, + }; + const dataClient = { + async callAsInternalUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await dataClient$.pipe(take(1)).toPromise(); + return await client.callAsInternalUser(endpoint, clientParams, options); + }, + asScoped(request: ScopeableRequest) { + return { + callAsInternalUser: dataClient.callAsInternalUser, + async callAsCurrentUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const client = await dataClient$.pipe(take(1)).toPromise(); + return await client + .asScoped(request) + .callAsCurrentUser(endpoint, clientParams, options); + }, + }; + }, + }; + return { legacy: { config$: clients$.pipe(map(clients => clients.config)) }, - adminClient$: clients$.pipe(map(clients => clients.adminClient)), - dataClient$: clients$.pipe(map(clients => clients.dataClient)), + adminClient$, + dataClient$, + adminClient, + dataClient, createClient: (type: string, clientConfig: Partial = {}) => { const finalConfig = merge({}, config, clientConfig); diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index 1f99f86d9887bb..5d64fadfaa1842 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -18,7 +18,13 @@ */ export { ElasticsearchService } from './elasticsearch_service'; -export { IClusterClient, ClusterClient, FakeRequest } from './cluster_client'; +export { + ClusterClient, + FakeRequest, + IClusterClient, + ICustomClusterClient, + ScopeableRequest, +} from './cluster_client'; export { IScopedClusterClient, ScopedClusterClient, Headers } from './scoped_cluster_client'; export { ElasticsearchClientConfig } from './elasticsearch_client_config'; export { config } from './elasticsearch_config'; diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index 505b57c7c9e8ec..22340bf3f2fc62 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -20,7 +20,7 @@ import { Observable } from 'rxjs'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; -import { IClusterClient } from './cluster_client'; +import { IClusterClient, ICustomClusterClient } from './cluster_client'; /** * @public @@ -46,29 +46,29 @@ export interface ElasticsearchServiceSetup { readonly createClient: ( type: string, clientConfig?: Partial - ) => IClusterClient; + ) => ICustomClusterClient; /** - * Observable of clients for the `admin` cluster. Observable emits when Elasticsearch config changes on the Kibana - * server. See {@link IClusterClient}. + * A client for the `admin` cluster. All Elasticsearch config value changes are processed under the hood. + * See {@link IClusterClient}. * - * @exmaple + * @example * ```js - * const client = await elasticsearch.adminClient$.pipe(take(1)).toPromise(); + * const client = core.elasticsearch.adminClient; * ``` */ - readonly adminClient$: Observable; + readonly adminClient: IClusterClient; /** - * Observable of clients for the `data` cluster. Observable emits when Elasticsearch config changes on the Kibana - * server. See {@link IClusterClient}. + * A client for the `data` cluster. All Elasticsearch config value changes are processed under the hood. + * See {@link IClusterClient}. * - * @exmaple + * @example * ```js - * const client = await elasticsearch.dataClient$.pipe(take(1)).toPromise(); + * const client = core.elasticsearch.dataClient; * ``` */ - readonly dataClient$: Observable; + readonly dataClient: IClusterClient; } /** @internal */ @@ -77,4 +77,7 @@ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceS readonly legacy: { readonly config$: Observable; }; + + readonly adminClient$: Observable; + readonly dataClient$: Observable; } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 953fa0738597c0..eccf3985fc495a 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -74,6 +74,7 @@ export { CspConfig, ICspConfig } from './csp'; export { ClusterClient, IClusterClient, + ICustomClusterClient, Headers, ScopedClusterClient, IScopedClusterClient, @@ -83,6 +84,7 @@ export { ElasticsearchServiceSetup, APICaller, FakeRequest, + ScopeableRequest, } from './elasticsearch'; export * from './elasticsearch/api_types'; export { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index e17de7364ce59c..cc36b90ec526d7 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -249,8 +249,8 @@ export class LegacyService implements CoreService { capabilities: setupDeps.core.capabilities, context: setupDeps.core.context, elasticsearch: { - adminClient$: setupDeps.core.elasticsearch.adminClient$, - dataClient$: setupDeps.core.elasticsearch.dataClient$, + adminClient: setupDeps.core.elasticsearch.adminClient, + dataClient: setupDeps.core.elasticsearch.dataClient, createClient: setupDeps.core.elasticsearch.createClient, }, http: { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 53849b040c413c..073d380d3aa67b 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -107,7 +107,7 @@ function createCoreSetupMock() { const mock: MockedKeys = { capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), http: httpMock, savedObjects: savedObjectsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, @@ -131,7 +131,7 @@ function createInternalCoreSetupMock() { const setupDeps: InternalCoreSetup = { capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createInternalSetup(), http: httpServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), savedObjects: savedObjectsServiceMock.createSetupContract(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 6e9a7967e9ecaf..6d82a8d3ec6cf1 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -145,8 +145,8 @@ export function createPluginSetupContext( createContextContainer: deps.context.createContextContainer, }, elasticsearch: { - adminClient$: deps.elasticsearch.adminClient$, - dataClient$: deps.elasticsearch.dataClient$, + adminClient: deps.elasticsearch.adminClient, + dataClient: deps.elasticsearch.dataClient, createClient: deps.elasticsearch.createClient, }, http: { diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index f58939c58e85e4..deb6f98e17b7b7 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -23,7 +23,6 @@ import { SavedObjectsService, SavedObjectsSetupDeps } from './saved_objects_serv import { mockCoreContext } from '../core_context.mock'; // @ts-ignore Typescript doesn't know about the jest mock import { KibanaMigrator, mockKibanaMigratorInstance } from './migrations/kibana/kibana_migrator'; -import { of } from 'rxjs'; import * as legacyElasticsearch from 'elasticsearch'; import { Env } from '../config'; import { configServiceMock } from '../mocks'; @@ -49,7 +48,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ - elasticsearch: { adminClient$: of(clusterClient) }, + elasticsearch: { adminClient: clusterClient }, legacyPlugins: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; @@ -68,7 +67,7 @@ describe('SavedObjectsService', () => { }); const soService = new SavedObjectsService(coreContext); const coreSetup = ({ - elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, + elasticsearch: { adminClient: { callAsInternalUser: jest.fn() } }, legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; @@ -82,7 +81,7 @@ describe('SavedObjectsService', () => { const coreContext = mockCoreContext.create({ configService }); const soService = new SavedObjectsService(coreContext); const coreSetup = ({ - elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, + elasticsearch: { adminClient: { callAsInternalUser: jest.fn() } }, legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; @@ -96,7 +95,7 @@ describe('SavedObjectsService', () => { const coreContext = mockCoreContext.create({ configService }); const soService = new SavedObjectsService(coreContext); const coreSetup = ({ - elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, + elasticsearch: { adminClient: { callAsInternalUser: jest.fn() } }, legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 5079b4f633df26..69ca8306ca4da8 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -29,7 +29,7 @@ import { import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; import { LegacyServiceDiscoverPlugins } from '../legacy'; -import { ElasticsearchServiceSetup, APICaller } from '../elasticsearch'; +import { InternalElasticsearchServiceSetup, APICaller } from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; import { SavedObjectsConfigType } from './saved_objects_config'; @@ -174,7 +174,7 @@ export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceSta /** @internal */ export interface SavedObjectsSetupDeps { legacyPlugins: LegacyServiceDiscoverPlugins; - elasticsearch: ElasticsearchServiceSetup; + elasticsearch: InternalElasticsearchServiceSetup; } /** @internal */ @@ -206,8 +206,6 @@ export class SavedObjectsService const savedObjectSchemas = new SavedObjectsSchema(savedObjectsSchemasDefinition); - const adminClient = await setupDeps.elasticsearch.adminClient$.pipe(first()).toPromise(); - const kibanaConfig = await this.coreContext.configService .atPath('kibana') .pipe(first()) @@ -218,6 +216,8 @@ export class SavedObjectsService .pipe(first()) .toPromise(); + const adminClient = setupDeps.elasticsearch.adminClient; + const migrator = (this.migrator = new KibanaMigrator({ savedObjectSchemas, savedObjectMappings, diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 0b67727455333b..42f75e25171264 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -66,12 +66,6 @@ export type ISavedObjectsClientProvider = Pick< * Provider for the Scoped Saved Objects Client. * * @internal - * - * @internalRemarks Because `getClient` is synchronous the Client Provider does - * not support creating factories that react to new ES clients emitted from - * elasticsearch.adminClient$. The Client Provider therefore doesn't support - * configuration changes to the Elasticsearch client. TODO: revisit once we've - * closed https://github.com/elastic/kibana/pull/45796 */ export class SavedObjectsClientProvider { private readonly _wrapperFactories = new PriorityCollection<{ diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 35359c5d6c4179..e41f045622a42c 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -498,7 +498,7 @@ export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capa // @public export class ClusterClient implements IClusterClient { constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); - asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; + asScoped(request?: ScopeableRequest): IScopedClusterClient; callAsInternalUser: APICaller; close(): void; } @@ -669,9 +669,9 @@ export class ElasticsearchErrorHelpers { // @public (undocumented) export interface ElasticsearchServiceSetup { - readonly adminClient$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; - readonly dataClient$: Observable; + readonly adminClient: IClusterClient; + readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; + readonly dataClient: IClusterClient; } // @public (undocumented) @@ -752,7 +752,7 @@ export interface HttpServiceStart { export type IBasePath = Pick; // @public -export type IClusterClient = Pick; +export type IClusterClient = Pick; // @public export interface IContextContainer> { @@ -771,6 +771,9 @@ export interface ICspConfig { readonly warnLegacyBrowsers: boolean; } +// @public +export type ICustomClusterClient = Pick; + // @public export interface IKibanaResponse { // (undocumented) @@ -1878,6 +1881,9 @@ export interface SavedObjectsUpdateResponse => { + // it consumes elasticsearch observables to provide the same client throughout the context lifetime. const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); const savedObjectsClient = coreSetup.savedObjects.getScopedClient(req); @@ -226,8 +227,6 @@ export class Server { render: rendering.render.bind(rendering, req, uiSettingsClient), }, savedObjects: { - // Note: the client provider doesn't support new ES clients - // emitted from adminClient$ client: savedObjectsClient, }, elasticsearch: { diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 9321336f0f55eb..1c95e75396bcc2 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -69,7 +69,7 @@ export default { ], setupFilesAfterEnv: ['/src/dev/jest/setup/mocks.js'], coverageDirectory: '/target/kibana-coverage/jest', - coverageReporters: ['html', 'text'], + coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html', 'text'], moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], modulePathIgnorePatterns: ['__fixtures__/', 'target/'], testMatch: ['**/*.test.{js,ts,tsx}'], diff --git a/src/dev/renovate/config.ts b/src/dev/renovate/config.ts index 61feb4e47f0220..c7056ba69449ab 100644 --- a/src/dev/renovate/config.ts +++ b/src/dev/renovate/config.ts @@ -95,7 +95,7 @@ export const RENOVATE_CONFIG = { allowedVersions: group.allowedVersions || undefined, reviewers: group.reviewers || undefined, masterIssueApproval: group.autoOpenPr ? false : undefined, - })), + })).sort((a, b) => a.groupName.localeCompare(b.groupName)), // internal/local packages { diff --git a/src/dev/run_i18n_check.ts b/src/dev/run_i18n_check.ts index f686c18c848f25..97ea988b1de3aa 100644 --- a/src/dev/run_i18n_check.ts +++ b/src/dev/run_i18n_check.ts @@ -133,6 +133,7 @@ run( { flags: { allowUnexpected: true, + guessTypesForUnexpectedFlags: true, }, } ); diff --git a/src/dev/run_i18n_extract.ts b/src/dev/run_i18n_extract.ts index 5492b5cd6795c7..106c8f10cb5cca 100644 --- a/src/dev/run_i18n_extract.ts +++ b/src/dev/run_i18n_extract.ts @@ -93,6 +93,7 @@ run( { flags: { allowUnexpected: true, + guessTypesForUnexpectedFlags: true, }, } ); diff --git a/src/dev/run_i18n_integrate.ts b/src/dev/run_i18n_integrate.ts index e8cd020773419e..23d66fae9f26e4 100644 --- a/src/dev/run_i18n_integrate.ts +++ b/src/dev/run_i18n_integrate.ts @@ -121,6 +121,7 @@ run( { flags: { allowUnexpected: true, + guessTypesForUnexpectedFlags: true, }, } ); diff --git a/src/legacy/core_plugins/elasticsearch/index.js b/src/legacy/core_plugins/elasticsearch/index.js index 7fc00e27e5b5f2..da7b557e7ea190 100644 --- a/src/legacy/core_plugins/elasticsearch/index.js +++ b/src/legacy/core_plugins/elasticsearch/index.js @@ -16,9 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import { combineLatest } from 'rxjs'; -import { first, map } from 'rxjs/operators'; +import { first } from 'rxjs/operators'; import healthCheck from './lib/health_check'; import { Cluster } from './lib/cluster'; import { createProxy } from './lib/create_proxy'; @@ -36,19 +34,12 @@ export default function(kibana) { // All methods that ES plugin exposes are synchronous so we should get the first // value from all observables here to be able to synchronously return and create // cluster clients afterwards. - const [esConfig, adminCluster, dataCluster] = await combineLatest( - server.newPlatform.__internals.elasticsearch.legacy.config$, - server.newPlatform.setup.core.elasticsearch.adminClient$, - server.newPlatform.setup.core.elasticsearch.dataClient$ - ) - .pipe( - first(), - map(([config, adminClusterClient, dataClusterClient]) => [ - config, - new Cluster(adminClusterClient), - new Cluster(dataClusterClient), - ]) - ) + const { adminClient, dataClient } = server.newPlatform.setup.core.elasticsearch; + const adminCluster = new Cluster(adminClient); + const dataCluster = new Cluster(dataClient); + + const esConfig = await server.newPlatform.__internals.elasticsearch.legacy.config$ + .pipe(first()) .toPromise(); defaultVars = { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx index c2f716ff6c45a7..b83ac5b4a77955 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx @@ -19,6 +19,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; import moment from 'moment-timezone'; +import { unitOfTime } from 'moment'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; @@ -53,6 +54,58 @@ interface DiscoverHistogramState { chartsTheme: EuiChartThemeType['theme']; } +function findIntervalFromDuration( + dateValue: number, + esValue: number, + esUnit: unitOfTime.Base, + timeZone: string +) { + const date = moment.tz(dateValue, timeZone); + const startOfDate = moment.tz(date, timeZone).startOf(esUnit); + const endOfDate = moment + .tz(date, timeZone) + .startOf(esUnit) + .add(esValue, esUnit); + return endOfDate.valueOf() - startOfDate.valueOf(); +} + +function getIntervalInMs( + value: number, + esValue: number, + esUnit: unitOfTime.Base, + timeZone: string +): number { + switch (esUnit) { + case 's': + return 1000 * esValue; + case 'ms': + return 1 * esValue; + default: + return findIntervalFromDuration(value, esValue, esUnit, timeZone); + } +} + +export function findMinInterval( + xValues: number[], + esValue: number, + esUnit: string, + timeZone: string +): number { + return xValues.reduce((minInterval, currentXvalue, index) => { + let currentDiff = minInterval; + if (index > 0) { + currentDiff = Math.abs(xValues[index - 1] - currentXvalue); + } + const singleUnitInterval = getIntervalInMs( + currentXvalue, + esValue, + esUnit as unitOfTime.Base, + timeZone + ); + return Math.min(minInterval, singleUnitInterval, currentDiff); + }, Number.MAX_SAFE_INTEGER); +} + export class DiscoverHistogram extends Component { public static propTypes = { chartData: PropTypes.object, @@ -154,7 +207,7 @@ export class DiscoverHistogram extends Component - + , node ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js index 0909b6947895cf..f7e654fd3c76dd 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js @@ -29,7 +29,7 @@ import { uiModules } from 'ui/modules'; import { fatalError, toastNotifications } from 'ui/notify'; import 'ui/accessibility/kbn_ui_ace_keyboard_mode'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { isNumeric } from 'ui/utils/numeric'; +import { isNumeric } from './lib/numeric'; import { canViewInApp } from './lib/in_app_url'; import { castEsToKbnFieldTypeName } from '../../../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/utils/sort_prefix_first.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts similarity index 62% rename from src/legacy/ui/public/utils/sort_prefix_first.ts rename to src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts index 4d1a8d7f398668..bb749de8dcb715 100644 --- a/src/legacy/ui/public/utils/sort_prefix_first.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts @@ -17,17 +17,20 @@ * under the License. */ -import { partition } from 'lodash'; +import { keysToCamelCaseShallow } from './case_conversion'; -export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] { - if (!prefix) { - return array; - } - const lowerCasePrefix = ('' + prefix).toLowerCase(); +describe('keysToCamelCaseShallow', () => { + test("should convert all of an object's keys to camel case", () => { + const data = { + camelCase: 'camelCase', + 'kebab-case': 'kebabCase', + snake_case: 'snakeCase', + }; - const partitions = partition(array, entry => { - const value = ('' + (property ? entry[property] : entry)).toLowerCase(); - return value.startsWith(lowerCasePrefix); + const result = keysToCamelCaseShallow(data); + + expect(result.camelCase).toBe('camelCase'); + expect(result.kebabCase).toBe('kebabCase'); + expect(result.snakeCase).toBe('snakeCase'); }); - return [...partitions[0], ...partitions[1]]; -} +}); diff --git a/src/legacy/utils/case_conversion.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts similarity index 77% rename from src/legacy/utils/case_conversion.ts rename to src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts index e1c1317b26b142..718530eb3b6022 100644 --- a/src/legacy/utils/case_conversion.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts @@ -17,16 +17,8 @@ * under the License. */ -import _ from 'lodash'; - -export function keysToSnakeCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.snakeCase(key); - }); -} +import { mapKeys, camelCase } from 'lodash'; export function keysToCamelCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.camelCase(key); - }); + return mapKeys(object, (value, key) => camelCase(key)); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js index f982966d1e3145..caf2b5f5034405 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js @@ -18,7 +18,7 @@ */ import { kfetch } from 'ui/kfetch'; -import { keysToCamelCaseShallow } from 'ui/utils/case_conversion'; +import { keysToCamelCaseShallow } from './case_conversion'; export async function findObjects(findOptions) { const response = await kfetch({ @@ -26,5 +26,6 @@ export async function findObjects(findOptions) { pathname: '/api/kibana/management/saved_objects/_find', query: findOptions, }); + return keysToCamelCaseShallow(response); } diff --git a/src/legacy/ui/public/utils/numeric.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts similarity index 86% rename from src/legacy/ui/public/utils/numeric.ts rename to src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts index 1342498cf5dc3a..c7bc6c26a378ff 100644 --- a/src/legacy/ui/public/utils/numeric.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts @@ -17,8 +17,8 @@ * under the License. */ -import _ from 'lodash'; +import { isNaN } from 'lodash'; export function isNumeric(v: any): boolean { - return !_.isNaN(v) && (typeof v === 'number' || (!Array.isArray(v) && !_.isNaN(parseFloat(v)))); + return !isNaN(v) && (typeof v === 'number' || (!Array.isArray(v) && !isNaN(parseFloat(v)))); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js index 0155b10f60218e..c64b332e8ebee3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js @@ -17,9 +17,10 @@ * under the License. */ -import { StringUtils } from 'ui/utils/string_utils'; import { i18n } from '@kbn/i18n'; +const upperFirst = (str = '') => str.replace(/^./, str => str.toUpperCase()); + const names = { general: i18n.translate('kbn.management.settings.categoryNames.generalLabel', { defaultMessage: 'General', @@ -51,5 +52,5 @@ const names = { }; export function getCategoryName(category) { - return category ? names[category] || StringUtils.upperFirst(category) : ''; + return category ? names[category] || upperFirst(category) : ''; } diff --git a/src/legacy/ui/public/utils/supports.js b/src/legacy/core_plugins/tile_map/public/css_filters.js similarity index 64% rename from src/legacy/ui/public/utils/supports.js rename to src/legacy/core_plugins/tile_map/public/css_filters.js index 19c5f34d97f36f..63d6a358059b3a 100644 --- a/src/legacy/ui/public/utils/supports.js +++ b/src/legacy/core_plugins/tile_map/public/css_filters.js @@ -22,22 +22,21 @@ import _ from 'lodash'; /** * just a place to put feature detection checks */ -export const supports = { - cssFilters: (function() { - const e = document.createElement('img'); - const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter']; - const test = 'grayscale(1)'; - rules.forEach(function(rule) { - e.style[rule] = test; - }); +export const supportsCssFilters = (function() { + const e = document.createElement('img'); + const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter']; + const test = 'grayscale(1)'; - document.body.appendChild(e); - const styles = window.getComputedStyle(e); - const can = _(styles) - .pick(rules) - .includes(test); - document.body.removeChild(e); + rules.forEach(function(rule) { + e.style[rule] = test; + }); - return can; - })(), -}; + document.body.appendChild(e); + const styles = window.getComputedStyle(e); + const can = _(styles) + .pick(rules) + .includes(test); + document.body.removeChild(e); + + return can; +})(); diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index f2354614ac41ac..f2e6469e768e7e 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -20,7 +20,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { supports } from 'ui/utils/supports'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; @@ -29,6 +28,7 @@ import { createTileMapVisualization } from './tile_map_visualization'; import { Status } from '../../visualizations/public'; import { TileMapOptions } from './components/tile_map_options'; import { MapTypes } from './map_types'; +import { supportsCssFilters } from './css_filters'; export function createTileMapTypeDefinition(dependencies) { const CoordinateMapsVisualization = createTileMapVisualization(dependencies); @@ -44,7 +44,7 @@ export function createTileMapTypeDefinition(dependencies) { defaultMessage: 'Plot latitude and longitude coordinates on a map', }), visConfig: { - canDesaturate: !!supports.cssFilters, + canDesaturate: Boolean(supportsCssFilters), defaults: { colorSchema: 'Yellow to Red', mapType: 'Scaled Circle Markers', diff --git a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js index c0092ca49c4f34..111db0a83ffc41 100644 --- a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js +++ b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js @@ -19,12 +19,12 @@ import _ from 'lodash'; import rison from 'rison-node'; -import { keyMap } from 'ui/utils/key_map'; import { uiModules } from 'ui/modules'; import 'ui/directives/input_focus'; import 'ui/directives/paginate'; import savedObjectFinderTemplate from './saved_object_finder.html'; import { savedSheetLoader } from '../services/saved_sheets'; +import { keyMap } from 'ui/directives/key_map'; const module = uiModules.get('kibana'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js b/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js index 46dab92f3b2459..2dbcdc4749a660 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js @@ -17,9 +17,9 @@ * under the License. */ -import { parseInterval } from 'ui/utils/parse_interval'; import { GTE_INTERVAL_RE } from '../../common/interval_regexp'; import { i18n } from '@kbn/i18n'; +import { parseInterval } from '../../../../../plugins/data/public'; export function validateInterval(bounds, panel, maxBuckets) { const { interval } = panel; diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx b/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx index 71a88b47a8be3e..3d7fda990b2ae8 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx +++ b/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx @@ -34,12 +34,12 @@ function VegaActionsMenu({ formatHJson, formatJson }: VegaActionsMenuProps) { const onHJsonCLick = useCallback(() => { formatHJson(); setIsPopoverOpen(false); - }, [isPopoverOpen, formatHJson]); + }, [formatHJson]); const onJsonCLick = useCallback(() => { formatJson(); setIsPopoverOpen(false); - }, [isPopoverOpen, formatJson]); + }, [formatJson]); const closePopover = useCallback(() => setIsPopoverOpen(false), []); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index 59c6bddb64521d..cc2ab133941db5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -445,6 +445,8 @@ export const buildVislibDimensions = async ( dimensions.x.params.date = true; const { esUnit, esValue } = xAgg.buckets.getInterval(); dimensions.x.params.interval = moment.duration(esValue, esUnit); + dimensions.x.params.intervalESValue = esValue; + dimensions.x.params.intervalESUnit = esUnit; dimensions.x.params.format = xAgg.buckets.getScaledDateFormat(); dimensions.x.params.bounds = xAgg.buckets.getBounds(); } else if (xAgg.type.name === 'histogram') { diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index a18cb7de5a61b9..a53e8e0498c42a 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -181,7 +181,7 @@ export default () => .default('localhost'), watchPrebuild: Joi.boolean().default(false), watchProxyTimeout: Joi.number().default(10 * 60000), - useBundleCache: Joi.boolean().default(Joi.ref('$prod')), + useBundleCache: Joi.boolean().default(!!process.env.CODE_COVERAGE ? true : Joi.ref('$prod')), sourceMaps: Joi.when('$prod', { is: true, then: Joi.boolean().valid(false), diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 0af6dacee59c81..8da1b3b05fa764 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -129,7 +129,7 @@ export interface KibanaCore { plugins: PluginsSetup; }; startDeps: { - core: CoreSetup; + core: CoreStart; plugins: Record; }; logger: LoggerFactory; diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js index 0e96189db4650e..691878cf66d27a 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.test.js @@ -106,12 +106,7 @@ describe('Saved Objects Mixin', () => { newPlatform: { __internals: { elasticsearch: { - adminClient$: { - pipe: jest.fn().mockImplementation(() => ({ - toPromise: () => - Promise.resolve({ adminClient: { callAsInternalUser: mockCallCluster } }), - })), - }, + adminClient: { callAsInternalUser: mockCallCluster }, }, }, }, diff --git a/src/legacy/server/status/lib/case_conversion.test.ts b/src/legacy/server/status/lib/case_conversion.test.ts new file mode 100644 index 00000000000000..a231ee0ba4b0fd --- /dev/null +++ b/src/legacy/server/status/lib/case_conversion.test.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { keysToSnakeCaseShallow } from './case_conversion'; + +describe('keysToSnakeCaseShallow', () => { + test("should convert all of an object's keys to snake case", () => { + const data = { + camelCase: 'camel_case', + 'kebab-case': 'kebab_case', + snake_case: 'snake_case', + }; + + const result = keysToSnakeCaseShallow(data); + + expect(result.camel_case).toBe('camel_case'); + expect(result.kebab_case).toBe('kebab_case'); + expect(result.snake_case).toBe('snake_case'); + }); +}); diff --git a/src/legacy/server/status/lib/case_conversion.ts b/src/legacy/server/status/lib/case_conversion.ts new file mode 100644 index 00000000000000..a3ae15028daeb2 --- /dev/null +++ b/src/legacy/server/status/lib/case_conversion.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 { mapKeys, snakeCase } from 'lodash'; + +export function keysToSnakeCaseShallow(object: Record) { + return mapKeys(object, (value, key) => snakeCase(key)); +} diff --git a/src/legacy/server/status/lib/metrics.js b/src/legacy/server/status/lib/metrics.js index 322d8d8bd9ab44..2631b245e72ab4 100644 --- a/src/legacy/server/status/lib/metrics.js +++ b/src/legacy/server/status/lib/metrics.js @@ -20,7 +20,7 @@ import os from 'os'; import v8 from 'v8'; import { get, isObject, merge } from 'lodash'; -import { keysToSnakeCaseShallow } from '../../../utils/case_conversion'; +import { keysToSnakeCaseShallow } from './case_conversion'; import { getAllStats as cGroupStats } from './cgroup'; import { getOSInfo } from './get_os_info'; diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index 98675402b43ccc..747ad025ef691d 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -20,6 +20,7 @@ @import './saved_objects/index'; @import './share/index'; @import './style_compile/index'; +@import '../../../plugins/management/public/components/index'; // The following are prefixed with "vis" diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js index 929aa4d5a7a9f5..a8512edee658b5 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js +++ b/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js @@ -104,6 +104,8 @@ describe('initXAxis', function() { it('reads the date interval param from the x agg', function() { chart.aspects.x[0].params.interval = 'P1D'; + chart.aspects.x[0].params.intervalESValue = 1; + chart.aspects.x[0].params.intervalESUnit = 'd'; chart.aspects.x[0].params.date = true; initXAxis(chart, table); expect(chart) @@ -113,6 +115,8 @@ describe('initXAxis', function() { expect(moment.isDuration(chart.ordered.interval)).to.be(true); expect(chart.ordered.interval.toISOString()).to.eql('P1D'); + expect(chart.ordered.intervalESValue).to.be(1); + expect(chart.ordered.intervalESUnit).to.be('d'); }); it('reads the numeric interval param from the x agg', function() { diff --git a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js b/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js index 531c564ea19d6f..4a81486783b085 100644 --- a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js +++ b/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js @@ -28,9 +28,19 @@ export function initXAxis(chart, table) { chart.xAxisFormat = format; chart.xAxisLabel = title; - if (params.interval) { - chart.ordered = { - interval: params.date ? moment.duration(params.interval) : params.interval, - }; + const { interval, date } = params; + if (interval) { + if (date) { + const { intervalESUnit, intervalESValue } = params; + chart.ordered = { + interval: moment.duration(interval), + intervalESUnit: intervalESUnit, + intervalESValue: intervalESValue, + }; + } else { + chart.ordered = { + interval, + }; + } } } diff --git a/src/legacy/ui/public/utils/key_map.ts b/src/legacy/ui/public/directives/key_map.ts similarity index 100% rename from src/legacy/ui/public/utils/key_map.ts rename to src/legacy/ui/public/directives/key_map.ts diff --git a/src/legacy/ui/public/directives/watch_multi/watch_multi.js b/src/legacy/ui/public/directives/watch_multi/watch_multi.js index 2a2ffe24cba9c2..54b5cf08a93978 100644 --- a/src/legacy/ui/public/directives/watch_multi/watch_multi.js +++ b/src/legacy/ui/public/directives/watch_multi/watch_multi.js @@ -19,7 +19,6 @@ import _ from 'lodash'; import { uiModules } from '../../modules'; -import { callEach } from '../../utils/function'; export function watchMultiDecorator($provide) { $provide.decorator('$rootScope', function($delegate) { @@ -112,7 +111,9 @@ export function watchMultiDecorator($provide) { ) ); - return _.partial(callEach, unwatchers); + return function() { + unwatchers.forEach(listener => listener()); + }; }; function normalizeExpression($scope, expr) { diff --git a/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js b/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js index 942f39fc98dec1..12bf5c1cce0043 100644 --- a/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js +++ b/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js @@ -30,6 +30,8 @@ import { EuiTitle, EuiCallOut, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; const { SearchBar } = npStart.plugins.data.ui; @@ -116,7 +118,13 @@ export class TestScript extends Component { if (previewData.error) { return ( - + -

First 10 results

+

+ +

{ - return !field.name.startsWith('_'); + const isMultiField = field.subType && field.subType.multi; + return !field.name.startsWith('_') && !isMultiField && !field.scripted; }) .forEach(field => { if (fieldsByTypeMap.has(field.type)) { @@ -180,9 +194,16 @@ export class TestScript extends Component { return ( - + - Run script + } /> @@ -219,11 +243,19 @@ export class TestScript extends Component { -

Preview results

+

+ +

- Run your script to preview the first 10 results. You can also select some additional - fields to include in your results to gain more context or add a query to filter on - specific documents. +

diff --git a/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts b/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts new file mode 100644 index 00000000000000..fc4ca8469382ad --- /dev/null +++ b/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { groupBy } from 'lodash'; +import { organizeBy } from './organize_by'; + +describe('organizeBy', () => { + test('it works', () => { + const col = [ + { + name: 'one', + roles: ['user', 'admin', 'owner'], + }, + { + name: 'two', + roles: ['user'], + }, + { + name: 'three', + roles: ['user'], + }, + { + name: 'four', + roles: ['user', 'admin'], + }, + ]; + + const resp = organizeBy(col, 'roles'); + expect(resp).toHaveProperty('user'); + expect(resp.user.length).toBe(4); + + expect(resp).toHaveProperty('admin'); + expect(resp.admin.length).toBe(2); + + expect(resp).toHaveProperty('owner'); + expect(resp.owner.length).toBe(1); + }); + + test('behaves just like groupBy in normal scenarios', () => { + const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }]; + + const orgs = organizeBy(col, 'name'); + const groups = groupBy(col, 'name'); + + expect(orgs).toEqual(groups); + }); +}); diff --git a/src/legacy/ui/public/indexed_array/helpers/organize_by.ts b/src/legacy/ui/public/indexed_array/helpers/organize_by.ts new file mode 100644 index 00000000000000..e923767c892cd5 --- /dev/null +++ b/src/legacy/ui/public/indexed_array/helpers/organize_by.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { each, isFunction } from 'lodash'; + +/** + * Like _.groupBy, but allows specifying multiple groups for a + * single object. + * + * organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') + * // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]} + * + * _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') + * // Object {'1,2,3': Array[1], '1,4': Array[1]} + * + * @param {array} collection - the list of values to organize + * @param {Function} callback - either a property name, or a callback. + * @return {object} + */ +export function organizeBy(collection: object[], callback: ((obj: object) => string) | string) { + const buckets: { [key: string]: object[] } = {}; + + function add(key: string, obj: object) { + if (!buckets[key]) { + buckets[key] = []; + } + buckets[key].push(obj); + } + + each(collection, (obj: Record) => { + const keys = isFunction(callback) ? callback(obj) : obj[callback]; + + if (!Array.isArray(keys)) { + add(keys, obj); + return; + } + + let length = keys.length; + while (length-- > 0) { + add(keys[length], obj); + } + }); + + return buckets; +} diff --git a/src/legacy/ui/public/indexed_array/indexed_array.js b/src/legacy/ui/public/indexed_array/indexed_array.js index 96b37a1423be09..39c79b2f021a39 100644 --- a/src/legacy/ui/public/indexed_array/indexed_array.js +++ b/src/legacy/ui/public/indexed_array/indexed_array.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import { inflector } from './inflector'; -import { organizeBy } from '../utils/collection'; +import { organizeBy } from './helpers/organize_by'; const pathGetter = _(_.get) .rearg(1, 0) diff --git a/src/legacy/ui/public/management/_index.scss b/src/legacy/ui/public/management/_index.scss deleted file mode 100644 index 30ac0c9fe9b27d..00000000000000 --- a/src/legacy/ui/public/management/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './components/index'; \ No newline at end of file diff --git a/src/legacy/ui/public/management/components/__snapshots__/sidebar_nav.test.ts.snap b/src/legacy/ui/public/management/components/__snapshots__/sidebar_nav.test.ts.snap deleted file mode 100644 index 3364bee33a5443..00000000000000 --- a/src/legacy/ui/public/management/components/__snapshots__/sidebar_nav.test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Management filters and filters and maps section objects into SidebarNav items 1`] = ` -Array [ - Object { - "data-test-subj": "activeSection", - "href": undefined, - "icon": null, - "id": "activeSection", - "isSelected": false, - "items": Array [ - Object { - "data-test-subj": "item", - "href": undefined, - "icon": null, - "id": "item", - "isSelected": false, - "name": "item", - }, - ], - "name": "activeSection", - }, -] -`; diff --git a/src/legacy/ui/public/management/components/sidebar_nav.tsx b/src/legacy/ui/public/management/components/sidebar_nav.tsx deleted file mode 100644 index cd3d85090dce01..00000000000000 --- a/src/legacy/ui/public/management/components/sidebar_nav.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon, EuiSideNav, IconType, EuiScreenReaderOnly } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { IndexedArray } from 'ui/indexed_array'; - -interface Subsection { - disabled: boolean; - visible: boolean; - id: string; - display: string; - url?: string; - icon?: IconType; -} -interface Section extends Subsection { - visibleItems: IndexedArray; -} - -const sectionVisible = (section: Subsection) => !section.disabled && section.visible; -const sectionToNav = (selectedId: string) => ({ display, id, url, icon }: Subsection) => ({ - id, - name: display, - icon: icon ? : null, - isSelected: selectedId === id, - href: url, - 'data-test-subj': id, -}); - -export const sideNavItems = (sections: Section[], selectedId: string) => - sections - .filter(sectionVisible) - .filter(section => section.visibleItems.filter(sectionVisible).length) - .map(section => ({ - items: section.visibleItems.filter(sectionVisible).map(sectionToNav(selectedId)), - ...sectionToNav(selectedId)(section), - })); - -interface SidebarNavProps { - sections: Section[]; - selectedId: string; -} - -interface SidebarNavState { - isSideNavOpenOnMobile: boolean; -} - -export class SidebarNav extends React.Component { - constructor(props: SidebarNavProps) { - super(props); - this.state = { - isSideNavOpenOnMobile: false, - }; - } - - public render() { - const HEADER_ID = 'management-nav-header'; - - return ( - <> - -

- {i18n.translate('common.ui.management.nav.label', { - defaultMessage: 'Management', - })} -

-
- - - ); - } - - private renderMobileTitle() { - return ; - } - - private toggleOpenOnMobile = () => { - this.setState({ - isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile, - }); - }; -} diff --git a/src/legacy/ui/public/management/index.js b/src/legacy/ui/public/management/index.js index ed8ddb65315e2c..b2f1946dbc59cb 100644 --- a/src/legacy/ui/public/management/index.js +++ b/src/legacy/ui/public/management/index.js @@ -23,8 +23,6 @@ export { PAGE_FOOTER_COMPONENT, } from '../../../core_plugins/kibana/public/management/sections/settings/components/default_component_registry'; export { registerSettingsComponent } from '../../../core_plugins/kibana/public/management/sections/settings/components/component_registry'; -export { SidebarNav } from './components'; export { MANAGEMENT_BREADCRUMB } from './breadcrumbs'; - import { npStart } from 'ui/new_platform'; export const management = npStart.plugins.management.legacy; diff --git a/src/legacy/ui/public/saved_objects/helpers/string_utils.test.ts b/src/legacy/ui/public/saved_objects/helpers/string_utils.test.ts new file mode 100644 index 00000000000000..5b108a4cc0180f --- /dev/null +++ b/src/legacy/ui/public/saved_objects/helpers/string_utils.test.ts @@ -0,0 +1,32 @@ +/* + * 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 { StringUtils } from './string_utils'; + +describe('StringUtils class', () => { + describe('static upperFirst', () => { + test('should converts the first character of string to upper case', () => { + expect(StringUtils.upperFirst()).toBe(''); + expect(StringUtils.upperFirst('')).toBe(''); + + expect(StringUtils.upperFirst('Fred')).toBe('Fred'); + expect(StringUtils.upperFirst('fred')).toBe('Fred'); + expect(StringUtils.upperFirst('FRED')).toBe('FRED'); + }); + }); +}); diff --git a/src/legacy/ui/public/utils/string_utils.ts b/src/legacy/ui/public/saved_objects/helpers/string_utils.ts similarity index 94% rename from src/legacy/ui/public/utils/string_utils.ts rename to src/legacy/ui/public/saved_objects/helpers/string_utils.ts index 22a57aeb07933a..fb10b792b7e69a 100644 --- a/src/legacy/ui/public/utils/string_utils.ts +++ b/src/legacy/ui/public/saved_objects/helpers/string_utils.ts @@ -23,7 +23,7 @@ export class StringUtils { * @param str {string} * @returns {string} */ - public static upperFirst(str: string): string { + public static upperFirst(str: string = ''): string { return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; } } diff --git a/src/legacy/ui/public/saved_objects/saved_object_loader.ts b/src/legacy/ui/public/saved_objects/saved_object_loader.ts index eb880ce5380c0c..7784edc9ad5288 100644 --- a/src/legacy/ui/public/saved_objects/saved_object_loader.ts +++ b/src/legacy/ui/public/saved_objects/saved_object_loader.ts @@ -18,7 +18,7 @@ */ import { SavedObject } from 'ui/saved_objects/types'; import { ChromeStart, SavedObjectsClientContract, SavedObjectsFindOptions } from 'kibana/public'; -import { StringUtils } from '../utils/string_utils'; +import { StringUtils } from './helpers/string_utils'; /** * The SavedObjectLoader class provides some convenience functions diff --git a/src/legacy/ui/public/state_management/app_state.js b/src/legacy/ui/public/state_management/app_state.js index 4bf386febf8361..e253d49c04131a 100644 --- a/src/legacy/ui/public/state_management/app_state.js +++ b/src/legacy/ui/public/state_management/app_state.js @@ -31,7 +31,6 @@ import { uiModules } from '../modules'; import { StateProvider } from './state'; import '../persisted_state'; import { createLegacyClass } from '../utils/legacy_class'; -import { callEach } from '../utils/function'; const urlParam = '_a'; @@ -62,7 +61,8 @@ export function AppStateProvider(Private, $location, $injector) { AppState.prototype.destroy = function() { AppState.Super.prototype.destroy.call(this); AppState.getAppState._set(null); - callEach(eventUnsubscribers); + + eventUnsubscribers.forEach(listener => listener()); }; /** diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index a9898303fa5be5..289d4b8006cba9 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -29,12 +29,11 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import angular from 'angular'; import rison from 'rison-node'; -import { applyDiff } from '../utils/diff_object'; +import { applyDiff } from './utils/diff_object'; import { EventsProvider } from '../events'; import { fatalError, toastNotifications } from '../notify'; import './config_provider'; import { createLegacyClass } from '../utils/legacy_class'; -import { callEach } from '../utils/function'; import { hashedItemStore, isStateHash, @@ -66,7 +65,7 @@ export function StateProvider( this._hashedItemStore = _hashedItemStore; // When the URL updates we need to fetch the values from the URL - this._cleanUpListeners = _.partial(callEach, [ + this._cleanUpListeners = [ // partial route update, no app reload $rootScope.$on('$routeUpdate', () => { this.fetch(); @@ -85,7 +84,7 @@ export function StateProvider( this.fetch(); } }), - ]); + ]; // Initialize the State with fetch this.fetch(); @@ -242,7 +241,9 @@ export function StateProvider( */ State.prototype.destroy = function() { this.off(); // removes all listeners - this._cleanUpListeners(); // Removes the $routeUpdate listener + + // Removes the $routeUpdate listener + this._cleanUpListeners.forEach(listener => listener(this)); }; State.prototype.setDefaults = function(defaults) { diff --git a/src/legacy/ui/public/utils/__tests__/diff_object.js b/src/legacy/ui/public/state_management/utils/diff_object.test.ts similarity index 57% rename from src/legacy/ui/public/utils/__tests__/diff_object.js rename to src/legacy/ui/public/state_management/utils/diff_object.test.ts index 8459aa60436b16..d93fa0fe5a1697 100644 --- a/src/legacy/ui/public/utils/__tests__/diff_object.js +++ b/src/legacy/ui/public/state_management/utils/diff_object.test.ts @@ -17,76 +17,88 @@ * under the License. */ -import expect from '@kbn/expect'; -import _ from 'lodash'; -import { applyDiff } from '../diff_object'; +import { cloneDeep } from 'lodash'; +import { applyDiff } from './diff_object'; -describe('ui/utils/diff_object', function() { - it('should list the removed keys', function() { +describe('diff_object', () => { + test('should list the removed keys', () => { const target = { test: 'foo' }; const source = { foo: 'test' }; const results = applyDiff(target, source); - expect(results).to.have.property('removed'); - expect(results.removed).to.eql(['test']); + + expect(results).toHaveProperty('removed'); + expect(results.removed).toEqual(['test']); }); - it('should list the changed keys', function() { + test('should list the changed keys', () => { const target = { foo: 'bar' }; const source = { foo: 'test' }; const results = applyDiff(target, source); - expect(results).to.have.property('changed'); - expect(results.changed).to.eql(['foo']); + + expect(results).toHaveProperty('changed'); + expect(results.changed).toEqual(['foo']); }); - it('should list the added keys', function() { + test('should list the added keys', () => { const target = {}; const source = { foo: 'test' }; const results = applyDiff(target, source); - expect(results).to.have.property('added'); - expect(results.added).to.eql(['foo']); + + expect(results).toHaveProperty('added'); + expect(results.added).toEqual(['foo']); }); - it('should list all the keys that are change or removed', function() { + test('should list all the keys that are change or removed', () => { const target = { foo: 'bar', test: 'foo' }; const source = { foo: 'test' }; const results = applyDiff(target, source); - expect(results).to.have.property('keys'); - expect(results.keys).to.eql(['foo', 'test']); + + expect(results).toHaveProperty('keys'); + expect(results.keys).toEqual(['foo', 'test']); }); - it('should ignore functions', function() { + test('should ignore functions', () => { const target = { foo: 'bar', test: 'foo' }; - const source = { foo: 'test', fn: _.noop }; + const source = { foo: 'test', fn: () => {} }; + applyDiff(target, source); - expect(target).to.not.have.property('fn'); + + expect(target).not.toHaveProperty('fn'); }); - it('should ignore underscores', function() { + test('should ignore underscores', () => { const target = { foo: 'bar', test: 'foo' }; const source = { foo: 'test', _private: 'foo' }; + applyDiff(target, source); - expect(target).to.not.have.property('_private'); + + expect(target).not.toHaveProperty('_private'); }); - it('should ignore dollar signs', function() { + test('should ignore dollar signs', () => { const target = { foo: 'bar', test: 'foo' }; const source = { foo: 'test', $private: 'foo' }; + applyDiff(target, source); - expect(target).to.not.have.property('$private'); + + expect(target).not.toHaveProperty('$private'); }); - it('should not list any changes for similar objects', function() { + test('should not list any changes for similar objects', () => { const target = { foo: 'bar', test: 'foo' }; const source = { foo: 'bar', test: 'foo', $private: 'foo' }; const results = applyDiff(target, source); - expect(results.changed).to.be.empty(); + + expect(results.changed).toEqual([]); }); - it('should only change keys that actually changed', function() { + test('should only change keys that actually changed', () => { const obj = { message: 'foo' }; - const target = { obj: obj, message: 'foo' }; - const source = { obj: _.cloneDeep(obj), message: 'test' }; + const target = { obj, message: 'foo' }; + const source = { obj: cloneDeep(obj), message: 'test' }; + applyDiff(target, source); - expect(target.obj).to.be(obj); + + expect(target.obj).toBe(obj); }); }); diff --git a/src/legacy/ui/public/state_management/utils/diff_object.ts b/src/legacy/ui/public/state_management/utils/diff_object.ts new file mode 100644 index 00000000000000..2590e2271f7718 --- /dev/null +++ b/src/legacy/ui/public/state_management/utils/diff_object.ts @@ -0,0 +1,75 @@ +/* + * 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 { keys, isFunction, difference, filter, union, pick, each, assign, isEqual } from 'lodash'; + +export interface IDiffObject { + removed: string[]; + added: string[]; + changed: string[]; + keys: string[]; +} + +/** + * Filter the private vars + * @param {string} key The keys + * @returns {boolean} + */ +const filterPrivateAndMethods = function(obj: Record) { + return function(key: string) { + if (isFunction(obj[key])) return false; + if (key.charAt(0) === '$') return false; + return key.charAt(0) !== '_'; + }; +}; + +export function applyDiff(target: Record, source: Record) { + const diff: IDiffObject = { + removed: [], + added: [], + changed: [], + keys: [], + }; + + const targetKeys = keys(target).filter(filterPrivateAndMethods(target)); + const sourceKeys = keys(source).filter(filterPrivateAndMethods(source)); + + // Find the keys to be removed + diff.removed = difference(targetKeys, sourceKeys); + + // Find the keys to be added + diff.added = difference(sourceKeys, targetKeys); + + // Find the keys that will be changed + diff.changed = filter(sourceKeys, key => !isEqual(target[key], source[key])); + + // Make a list of all the keys that are changing + diff.keys = union(diff.changed, diff.removed, diff.added); + + // Remove all the keys + each(diff.removed, key => { + delete target[key]; + }); + + // Assign the changed to the source to the target + assign(target, pick(source, diff.changed)); + // Assign the added to the source to the target + assign(target, pick(source, diff.added)); + + return diff; +} diff --git a/src/legacy/ui/public/time_buckets/time_buckets.js b/src/legacy/ui/public/time_buckets/time_buckets.js index 92de88b47e3c30..96ba4bfb2ac2c8 100644 --- a/src/legacy/ui/public/time_buckets/time_buckets.js +++ b/src/legacy/ui/public/time_buckets/time_buckets.js @@ -20,13 +20,12 @@ import _ from 'lodash'; import moment from 'moment'; import { npStart } from 'ui/new_platform'; -import { parseInterval } from '../utils/parse_interval'; import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval'; import { convertDurationToNormalizedEsInterval, convertIntervalToEsInterval, } from './calc_es_interval'; -import { FIELD_FORMAT_IDS } from '../../../../plugins/data/public'; +import { FIELD_FORMAT_IDS, parseInterval } from '../../../../plugins/data/public'; const getConfig = (...args) => npStart.core.uiSettings.get(...args); diff --git a/src/legacy/ui/public/utils/__tests__/collection.js b/src/legacy/ui/public/utils/__tests__/collection.js deleted file mode 100644 index 402f0387e53cea..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/collection.js +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { groupBy } from 'lodash'; -import { move, pushAll, organizeBy } from '../collection'; - -describe('collection', () => { - describe('move', function() { - it('accepts previous from->to syntax', function() { - const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1]; - - expect(list[3]).to.be(1); - expect(list[8]).to.be(8); - - move(list, 8, 3); - - expect(list[8]).to.be(1); - expect(list[3]).to.be(8); - }); - - it('moves an object up based on a function callback', function() { - const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; - - expect(list[4]).to.be(0); - expect(list[5]).to.be(1); - expect(list[6]).to.be(0); - - move(list, 5, false, function(v) { - return v === 0; - }); - - expect(list[4]).to.be(1); - expect(list[5]).to.be(0); - expect(list[6]).to.be(0); - }); - - it('moves an object down based on a function callback', function() { - const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; - - expect(list[4]).to.be(0); - expect(list[5]).to.be(1); - expect(list[6]).to.be(0); - - move(list, 5, true, function(v) { - return v === 0; - }); - - expect(list[4]).to.be(0); - expect(list[5]).to.be(0); - expect(list[6]).to.be(1); - }); - - it('moves an object up based on a where callback', function() { - const list = [ - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - ]; - - expect(list[4]).to.have.property('v', 0); - expect(list[5]).to.have.property('v', 1); - expect(list[6]).to.have.property('v', 0); - - move(list, 5, false, { v: 0 }); - - expect(list[4]).to.have.property('v', 1); - expect(list[5]).to.have.property('v', 0); - expect(list[6]).to.have.property('v', 0); - }); - - it('moves an object down based on a where callback', function() { - const list = [ - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - ]; - - expect(list[4]).to.have.property('v', 0); - expect(list[5]).to.have.property('v', 1); - expect(list[6]).to.have.property('v', 0); - - move(list, 5, true, { v: 0 }); - - expect(list[4]).to.have.property('v', 0); - expect(list[5]).to.have.property('v', 0); - expect(list[6]).to.have.property('v', 1); - }); - - it('moves an object down based on a pluck callback', function() { - const list = [ - { id: 0, normal: true }, - { id: 1, normal: true }, - { id: 2, normal: true }, - { id: 3, normal: true }, - { id: 4, normal: true }, - { id: 5, normal: false }, - { id: 6, normal: true }, - { id: 7, normal: true }, - { id: 8, normal: true }, - { id: 9, normal: true }, - ]; - - expect(list[4]).to.have.property('id', 4); - expect(list[5]).to.have.property('id', 5); - expect(list[6]).to.have.property('id', 6); - - move(list, 5, true, 'normal'); - - expect(list[4]).to.have.property('id', 4); - expect(list[5]).to.have.property('id', 6); - expect(list[6]).to.have.property('id', 5); - }); - }); - - describe('pushAll', function() { - it('pushes an entire array into another', function() { - const a = [1, 2, 3, 4]; - const b = [5, 6, 7, 8]; - - const output = pushAll(b, a); - expect(output).to.be(a); - expect(a).to.eql([1, 2, 3, 4, 5, 6, 7, 8]); - expect(b).to.eql([5, 6, 7, 8]); - }); - }); - - describe('organizeBy', function() { - it('it works', function() { - const col = [ - { - name: 'one', - roles: ['user', 'admin', 'owner'], - }, - { - name: 'two', - roles: ['user'], - }, - { - name: 'three', - roles: ['user'], - }, - { - name: 'four', - roles: ['user', 'admin'], - }, - ]; - - const resp = organizeBy(col, 'roles'); - expect(resp).to.have.property('user'); - expect(resp.user).to.have.length(4); - - expect(resp).to.have.property('admin'); - expect(resp.admin).to.have.length(2); - - expect(resp).to.have.property('owner'); - expect(resp.owner).to.have.length(1); - }); - - it('behaves just like groupBy in normal scenarios', function() { - const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }]; - - const orgs = organizeBy(col, 'name'); - const groups = groupBy(col, 'name'); - expect(orgs).to.eql(groups); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/parse_interval.js b/src/legacy/ui/public/utils/__tests__/parse_interval.js deleted file mode 100644 index a33b0ab958072c..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/parse_interval.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { parseInterval } from '../parse_interval'; -import expect from '@kbn/expect'; - -describe('parseInterval', function() { - it('should correctly parse an interval containing unit and value', function() { - let duration = parseInterval('1d'); - expect(duration.as('d')).to.be(1); - - duration = parseInterval('2y'); - expect(duration.as('y')).to.be(2); - - duration = parseInterval('5M'); - expect(duration.as('M')).to.be(5); - - duration = parseInterval('5m'); - expect(duration.as('m')).to.be(5); - - duration = parseInterval('250ms'); - expect(duration.as('ms')).to.be(250); - - duration = parseInterval('100s'); - expect(duration.as('s')).to.be(100); - - duration = parseInterval('23d'); - expect(duration.as('d')).to.be(23); - - duration = parseInterval('52w'); - expect(duration.as('w')).to.be(52); - }); - - it('should correctly parse fractional intervals containing unit and value', function() { - let duration = parseInterval('1.5w'); - expect(duration.as('w')).to.be(1.5); - - duration = parseInterval('2.35y'); - expect(duration.as('y')).to.be(2.35); - }); - - it('should correctly bubble up intervals which are less than 1', function() { - let duration = parseInterval('0.5y'); - expect(duration.as('d')).to.be(183); - - duration = parseInterval('0.5d'); - expect(duration.as('h')).to.be(12); - }); - - it('should correctly parse a unit in an interval only', function() { - let duration = parseInterval('ms'); - expect(duration.as('ms')).to.be(1); - - duration = parseInterval('d'); - expect(duration.as('d')).to.be(1); - - duration = parseInterval('m'); - expect(duration.as('m')).to.be(1); - - duration = parseInterval('y'); - expect(duration.as('y')).to.be(1); - - duration = parseInterval('M'); - expect(duration.as('M')).to.be(1); - }); - - it('should return null for an invalid interval', function() { - let duration = parseInterval(''); - expect(duration).to.not.be.ok(); - - duration = parseInterval(null); - expect(duration).to.not.be.ok(); - - duration = parseInterval('234asdf'); - expect(duration).to.not.be.ok(); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/sort_prefix_first.js b/src/legacy/ui/public/utils/__tests__/sort_prefix_first.js deleted file mode 100644 index 721de95cbd27db..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/sort_prefix_first.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { sortPrefixFirst } from '../sort_prefix_first'; - -describe('sortPrefixFirst', function() { - it('should return the original unmodified array if no prefix is provided', function() { - const array = ['foo', 'bar', 'baz']; - const result = sortPrefixFirst(array); - expect(result).to.be(array); - expect(result).to.eql(['foo', 'bar', 'baz']); - }); - - it('should sort items that match the prefix first without modifying the original array', function() { - const array = ['foo', 'bar', 'baz']; - const result = sortPrefixFirst(array, 'b'); - expect(result).to.not.be(array); - expect(result).to.eql(['bar', 'baz', 'foo']); - expect(array).to.eql(['foo', 'bar', 'baz']); - }); - - it('should not modify the order of the array other than matching prefix without modifying the original array', function() { - const array = ['foo', 'bar', 'baz', 'qux', 'quux']; - const result = sortPrefixFirst(array, 'b'); - expect(result).to.not.be(array); - expect(result).to.eql(['bar', 'baz', 'foo', 'qux', 'quux']); - expect(array).to.eql(['foo', 'bar', 'baz', 'qux', 'quux']); - }); - - it('should sort objects by property if provided', function() { - const array = [ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - { name: 'qux' }, - { name: 'quux' }, - ]; - const result = sortPrefixFirst(array, 'b', 'name'); - expect(result).to.not.be(array); - expect(result).to.eql([ - { name: 'bar' }, - { name: 'baz' }, - { name: 'foo' }, - { name: 'qux' }, - { name: 'quux' }, - ]); - expect(array).to.eql([ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - { name: 'qux' }, - { name: 'quux' }, - ]); - }); - - it('should handle numbers', function() { - const array = [1, 50, 5]; - const result = sortPrefixFirst(array, 5); - expect(result).to.not.be(array); - expect(result).to.eql([50, 5, 1]); - }); - - it('should handle mixed case', function() { - const array = ['Date Histogram', 'Histogram']; - const prefix = 'histo'; - const result = sortPrefixFirst(array, prefix); - expect(result).to.not.be(array); - expect(result).to.eql(['Histogram', 'Date Histogram']); - }); -}); diff --git a/src/legacy/ui/public/utils/collection.test.ts b/src/legacy/ui/public/utils/collection.test.ts new file mode 100644 index 00000000000000..0841e3554c0d09 --- /dev/null +++ b/src/legacy/ui/public/utils/collection.test.ts @@ -0,0 +1,141 @@ +/* + * 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 { move } from './collection'; + +describe('collection', () => { + describe('move', () => { + test('accepts previous from->to syntax', () => { + const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1]; + + expect(list[3]).toBe(1); + expect(list[8]).toBe(8); + + move(list, 8, 3); + + expect(list[8]).toBe(1); + expect(list[3]).toBe(8); + }); + + test('moves an object up based on a function callback', () => { + const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; + + expect(list[4]).toBe(0); + expect(list[5]).toBe(1); + expect(list[6]).toBe(0); + + move(list, 5, false, (v: any) => v === 0); + + expect(list[4]).toBe(1); + expect(list[5]).toBe(0); + expect(list[6]).toBe(0); + }); + + test('moves an object down based on a function callback', () => { + const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; + + expect(list[4]).toBe(0); + expect(list[5]).toBe(1); + expect(list[6]).toBe(0); + + move(list, 5, true, (v: any) => v === 0); + + expect(list[4]).toBe(0); + expect(list[5]).toBe(0); + expect(list[6]).toBe(1); + }); + + test('moves an object up based on a where callback', () => { + const list = [ + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + ]; + + expect(list[4]).toHaveProperty('v', 0); + expect(list[5]).toHaveProperty('v', 1); + expect(list[6]).toHaveProperty('v', 0); + + move(list, 5, false, { v: 0 }); + + expect(list[4]).toHaveProperty('v', 1); + expect(list[5]).toHaveProperty('v', 0); + expect(list[6]).toHaveProperty('v', 0); + }); + + test('moves an object down based on a where callback', () => { + const list = [ + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + ]; + + expect(list[4]).toHaveProperty('v', 0); + expect(list[5]).toHaveProperty('v', 1); + expect(list[6]).toHaveProperty('v', 0); + + move(list, 5, true, { v: 0 }); + + expect(list[4]).toHaveProperty('v', 0); + expect(list[5]).toHaveProperty('v', 0); + expect(list[6]).toHaveProperty('v', 1); + }); + + test('moves an object down based on a pluck callback', () => { + const list = [ + { id: 0, normal: true }, + { id: 1, normal: true }, + { id: 2, normal: true }, + { id: 3, normal: true }, + { id: 4, normal: true }, + { id: 5, normal: false }, + { id: 6, normal: true }, + { id: 7, normal: true }, + { id: 8, normal: true }, + { id: 9, normal: true }, + ]; + + expect(list[4]).toHaveProperty('id', 4); + expect(list[5]).toHaveProperty('id', 5); + expect(list[6]).toHaveProperty('id', 6); + + move(list, 5, true, 'normal'); + + expect(list[4]).toHaveProperty('id', 4); + expect(list[5]).toHaveProperty('id', 6); + expect(list[6]).toHaveProperty('id', 5); + }); + }); +}); diff --git a/src/legacy/ui/public/utils/collection.ts b/src/legacy/ui/public/utils/collection.ts index 61a7388575c936..45e5a0704c37bb 100644 --- a/src/legacy/ui/public/utils/collection.ts +++ b/src/legacy/ui/public/utils/collection.ts @@ -33,10 +33,10 @@ import _ from 'lodash'; * @return {array} - the objs argument */ export function move( - objs: object[], + objs: any[], obj: object | number, below: number | boolean, - qualifier: (object: object, index: number) => any + qualifier?: ((object: object, index: number) => any) | Record | string ): object[] { const origI = _.isNumber(obj) ? obj : objs.indexOf(obj); if (origI === -1) { @@ -50,7 +50,7 @@ export function move( } below = !!below; - qualifier = _.callback(qualifier); + qualifier = qualifier && _.callback(qualifier); const above = !below; const finder = below ? _.findIndex : _.findLastIndex; @@ -63,7 +63,7 @@ export function move( if (above && otherI >= origI) { return; } - return !!qualifier(otherAgg, otherI); + return Boolean(_.isFunction(qualifier) && qualifier(otherAgg, otherI)); }); if (targetI === -1) { @@ -74,68 +74,3 @@ export function move( objs.splice(targetI, 0, objs.splice(origI, 1)[0]); return objs; } - -/** - * Like _.groupBy, but allows specifying multiple groups for a - * single object. - * - * organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') - * // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]} - * - * _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') - * // Object {'1,2,3': Array[1], '1,4': Array[1]} - * - * @param {array} collection - the list of values to organize - * @param {Function} callback - either a property name, or a callback. - * @return {object} - */ -export function organizeBy(collection: object[], callback: (obj: object) => string | string) { - const buckets: { [key: string]: object[] } = {}; - const prop = typeof callback === 'function' ? false : callback; - - function add(key: string, obj: object) { - if (!buckets[key]) { - buckets[key] = []; - } - buckets[key].push(obj); - } - - _.each(collection, (obj: object) => { - const keys = prop === false ? callback(obj) : obj[prop]; - - if (!Array.isArray(keys)) { - add(keys, obj); - return; - } - - let length = keys.length; - while (length-- > 0) { - add(keys[length], obj); - } - }); - - return buckets; -} - -/** - * Efficient and safe version of [].push(dest, source); - * - * @param {Array} source - the array to pull values from - * @param {Array} dest - the array to push values into - * @return {Array} dest - */ -export function pushAll(source: any[], dest: any[]): any[] { - const start = dest.length; - const adding = source.length; - - // allocate - http://goo.gl/e2i0S0 - dest.length = start + adding; - - // fill sparse positions - let i = -1; - while (++i < adding) { - dest[start + i] = source[i]; - } - - return dest; -} diff --git a/src/legacy/ui/public/utils/diff_object.js b/src/legacy/ui/public/utils/diff_object.js deleted file mode 100644 index ddad5e0ae42a01..00000000000000 --- a/src/legacy/ui/public/utils/diff_object.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import angular from 'angular'; - -export function applyDiff(target, source) { - const diff = {}; - - /** - * Filter the private vars - * @param {string} key The keys - * @returns {boolean} - */ - const filterPrivateAndMethods = function(obj) { - return function(key) { - if (_.isFunction(obj[key])) return false; - if (key.charAt(0) === '$') return false; - return key.charAt(0) !== '_'; - }; - }; - - const targetKeys = _.keys(target).filter(filterPrivateAndMethods(target)); - const sourceKeys = _.keys(source).filter(filterPrivateAndMethods(source)); - - // Find the keys to be removed - diff.removed = _.difference(targetKeys, sourceKeys); - - // Find the keys to be added - diff.added = _.difference(sourceKeys, targetKeys); - - // Find the keys that will be changed - diff.changed = _.filter(sourceKeys, function(key) { - return !angular.equals(target[key], source[key]); - }); - - // Make a list of all the keys that are changing - diff.keys = _.union(diff.changed, diff.removed, diff.added); - - // Remove all the keys - _.each(diff.removed, function(key) { - delete target[key]; - }); - - // Assign the changed to the source to the target - _.assign(target, _.pick(source, diff.changed)); - // Assign the added to the source to the target - _.assign(target, _.pick(source, diff.added)); - - return diff; -} diff --git a/src/legacy/ui/public/utils/math.test.ts b/src/legacy/ui/public/utils/math.test.ts deleted file mode 100644 index 13f090e77647b0..00000000000000 --- a/src/legacy/ui/public/utils/math.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { greatestCommonDivisor, leastCommonMultiple } from './math'; - -describe('math utils', () => { - describe('greatestCommonDivisor', () => { - const tests: Array<[number, number, number]> = [ - [3, 5, 1], - [30, 36, 6], - [5, 1, 1], - [9, 9, 9], - [40, 20, 20], - [3, 0, 3], - [0, 5, 5], - [0, 0, 0], - [-9, -3, 3], - [-24, 8, 8], - [22, -7, 1], - ]; - - tests.map(([a, b, expected]) => { - it(`should return ${expected} for greatestCommonDivisor(${a}, ${b})`, () => { - expect(greatestCommonDivisor(a, b)).toBe(expected); - }); - }); - }); - - describe('leastCommonMultiple', () => { - const tests: Array<[number, number, number]> = [ - [3, 5, 15], - [1, 1, 1], - [5, 6, 30], - [3, 9, 9], - [8, 20, 40], - [5, 5, 5], - [0, 5, 0], - [-4, -5, 20], - [-2, -3, 6], - [-8, 2, 8], - [-8, 5, 40], - ]; - - tests.map(([a, b, expected]) => { - it(`should return ${expected} for leastCommonMultiple(${a}, ${b})`, () => { - expect(leastCommonMultiple(a, b)).toBe(expected); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts index 31fc0d90be1016..c7fb937b974245 100644 --- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts +++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts @@ -21,7 +21,7 @@ import { TimeIntervalParam } from 'ui/vis/editors/config/types'; import { AggConfig } from '../..'; import { AggType } from '../../../agg_types'; import { IndexPattern } from '../../../../../../plugins/data/public'; -import { leastCommonMultiple } from '../../../utils/math'; +import { leastCommonMultiple } from '../../lib/least_common_multiple'; import { parseEsInterval } from '../../../../../core_plugins/data/public'; import { leastCommonInterval } from '../../lib/least_common_interval'; import { EditorConfig, EditorParamConfig, FixedParam, NumericIntervalParam } from './types'; diff --git a/src/legacy/ui/public/vis/lib/least_common_interval.ts b/src/legacy/ui/public/vis/lib/least_common_interval.ts index dfdb099249228f..244bc1d0111e3b 100644 --- a/src/legacy/ui/public/vis/lib/least_common_interval.ts +++ b/src/legacy/ui/public/vis/lib/least_common_interval.ts @@ -18,7 +18,7 @@ */ import dateMath from '@elastic/datemath'; -import { leastCommonMultiple } from '../../utils/math'; +import { leastCommonMultiple } from './least_common_multiple'; import { parseEsInterval } from '../../../../core_plugins/data/public'; /** diff --git a/src/legacy/ui/public/utils/case_conversion.ts b/src/legacy/ui/public/vis/lib/least_common_multiple.test.ts similarity index 60% rename from src/legacy/ui/public/utils/case_conversion.ts rename to src/legacy/ui/public/vis/lib/least_common_multiple.test.ts index e0c4cad6b4e940..d07ac96b5f4d5b 100644 --- a/src/legacy/ui/public/utils/case_conversion.ts +++ b/src/legacy/ui/public/vis/lib/least_common_multiple.test.ts @@ -17,20 +17,26 @@ * under the License. */ -// TODO: This file is copied from src/legacy/utils/case_conversion.ts -// because TS-imports from utils in ui are currently not possible. -// When the build process is updated, this file can be removed +import { leastCommonMultiple } from './least_common_multiple'; -import _ from 'lodash'; +describe('leastCommonMultiple', () => { + const tests: Array<[number, number, number]> = [ + [3, 5, 15], + [1, 1, 1], + [5, 6, 30], + [3, 9, 9], + [8, 20, 40], + [5, 5, 5], + [0, 5, 0], + [-4, -5, 20], + [-2, -3, 6], + [-8, 2, 8], + [-8, 5, 40], + ]; -export function keysToSnakeCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.snakeCase(key); + tests.map(([a, b, expected]) => { + test(`should return ${expected} for leastCommonMultiple(${a}, ${b})`, () => { + expect(leastCommonMultiple(a, b)).toBe(expected); + }); }); -} - -export function keysToCamelCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.camelCase(key); - }); -} +}); diff --git a/src/legacy/ui/public/utils/math.ts b/src/legacy/ui/public/vis/lib/least_common_multiple.ts similarity index 95% rename from src/legacy/ui/public/utils/math.ts rename to src/legacy/ui/public/vis/lib/least_common_multiple.ts index e0cab4236e2b8d..dedddbf22ab44b 100644 --- a/src/legacy/ui/public/utils/math.ts +++ b/src/legacy/ui/public/vis/lib/least_common_multiple.ts @@ -24,8 +24,10 @@ * This method does not properly work for fractional (non integer) numbers. If you * pass in fractional numbers there usually will be an output, but that's not necessarily * the greatest common divisor of those two numbers. + * + * @private */ -export function greatestCommonDivisor(a: number, b: number): number { +function greatestCommonDivisor(a: number, b: number): number { return a === 0 ? Math.abs(b) : greatestCommonDivisor(b % a, a); } @@ -36,6 +38,8 @@ export function greatestCommonDivisor(a: number, b: number): number { * Since this calculation suffers from rounding issues in decimal values, this method * won't work for passing in fractional (non integer) numbers. It will return a value, * but that value won't necessarily be the mathematical correct least common multiple. + * + * @internal */ export function leastCommonMultiple(a: number, b: number): number { return Math.abs((a * b) / greatestCommonDivisor(a, b)); diff --git a/src/legacy/utils/case_conversion.test.ts b/src/legacy/utils/case_conversion.test.ts deleted file mode 100644 index 27498f38789800..00000000000000 --- a/src/legacy/utils/case_conversion.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from './case_conversion'; - -describe('keysToSnakeCaseShallow', () => { - it("should convert all of an object's keys to snake case", () => { - const result = keysToSnakeCaseShallow({ - camelCase: 'camel_case', - 'kebab-case': 'kebab_case', - snake_case: 'snake_case', - }); - - expect(result).toMatchInlineSnapshot(` -Object { - "camel_case": "camel_case", - "kebab_case": "kebab_case", - "snake_case": "snake_case", -} -`); - }); -}); - -describe('keysToCamelCaseShallow', () => { - it("should convert all of an object's keys to camel case", () => { - const result = keysToCamelCaseShallow({ - camelCase: 'camelCase', - 'kebab-case': 'kebabCase', - snake_case: 'snakeCase', - }); - - expect(result).toMatchInlineSnapshot(` -Object { - "camelCase": "camelCase", - "kebabCase": "kebabCase", - "snakeCase": "snakeCase", -} -`); - }); -}); diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss index b446f1e57a895d..bb958406769698 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss @@ -1,9 +1,7 @@ .dshDashboardViewport { - height: 100%; width: 100%; } .dshDashboardViewport-withMargins { width: 100%; - height: 100%; } diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 7196c96989e97d..c5f1276feb81dd 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -18,3 +18,4 @@ */ export { shortenDottedString } from './shorten_dotted_string'; +export { parseInterval } from './parse_interval'; diff --git a/src/plugins/data/common/utils/parse_interval.test.ts b/src/plugins/data/common/utils/parse_interval.test.ts new file mode 100644 index 00000000000000..0c02b02a25af0d --- /dev/null +++ b/src/plugins/data/common/utils/parse_interval.test.ts @@ -0,0 +1,119 @@ +/* + * 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 { Duration, unitOfTime } from 'moment'; +import { parseInterval } from './parse_interval'; + +const validateDuration = (duration: Duration | null, unit: unitOfTime.Base, value: number) => { + expect(duration).toBeDefined(); + + if (duration) { + expect(duration.as(unit)).toBe(value); + } +}; + +describe('parseInterval', () => { + describe('integer', () => { + test('should correctly parse 1d interval', () => { + validateDuration(parseInterval('1d'), 'd', 1); + }); + + test('should correctly parse 2y interval', () => { + validateDuration(parseInterval('2y'), 'y', 2); + }); + + test('should correctly parse 5M interval', () => { + validateDuration(parseInterval('5M'), 'M', 5); + }); + + test('should correctly parse 5m interval', () => { + validateDuration(parseInterval('5m'), 'm', 5); + }); + + test('should correctly parse 250ms interval', () => { + validateDuration(parseInterval('250ms'), 'ms', 250); + }); + + test('should correctly parse 100s interval', () => { + validateDuration(parseInterval('100s'), 's', 100); + }); + + test('should correctly parse 23d interval', () => { + validateDuration(parseInterval('23d'), 'd', 23); + }); + + test('should correctly parse 52w interval', () => { + validateDuration(parseInterval('52w'), 'w', 52); + }); + }); + + describe('fractional interval', () => { + test('should correctly parse fractional 2.35y interval', () => { + validateDuration(parseInterval('2.35y'), 'y', 2.35); + }); + + test('should correctly parse fractional 1.5w interval', () => { + validateDuration(parseInterval('1.5w'), 'w', 1.5); + }); + }); + + describe('less than 1', () => { + test('should correctly bubble up 0.5h interval which are less than 1', () => { + validateDuration(parseInterval('0.5h'), 'm', 30); + }); + + test('should correctly bubble up 0.5d interval which are less than 1', () => { + validateDuration(parseInterval('0.5d'), 'h', 12); + }); + }); + + describe('unit in an interval only', () => { + test('should correctly parse ms interval', () => { + validateDuration(parseInterval('ms'), 'ms', 1); + }); + + test('should correctly parse d interval', () => { + validateDuration(parseInterval('d'), 'd', 1); + }); + + test('should correctly parse m interval', () => { + validateDuration(parseInterval('m'), 'm', 1); + }); + + test('should correctly parse y interval', () => { + validateDuration(parseInterval('y'), 'y', 1); + }); + + test('should correctly parse M interval', () => { + validateDuration(parseInterval('M'), 'M', 1); + }); + }); + + test('should return null for an invalid interval', () => { + let duration = parseInterval(''); + expect(duration).toBeNull(); + + // @ts-ignore + duration = parseInterval(null); + expect(duration).toBeNull(); + + duration = parseInterval('234asdf'); + expect(duration).toBeNull(); + }); +}); diff --git a/src/legacy/ui/public/utils/parse_interval.js b/src/plugins/data/common/utils/parse_interval.ts similarity index 86% rename from src/legacy/ui/public/utils/parse_interval.js rename to src/plugins/data/common/utils/parse_interval.ts index fe484985ef101a..ef1d89e400b729 100644 --- a/src/legacy/ui/public/utils/parse_interval.js +++ b/src/plugins/data/common/utils/parse_interval.ts @@ -17,14 +17,14 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +import { find } from 'lodash'; +import moment, { unitOfTime } from 'moment'; import dateMath from '@elastic/datemath'; // Assume interval is in the form (value)(unit), such as "1h" const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$'); -export function parseInterval(interval) { +export function parseInterval(interval: string): moment.Duration | null { const matches = String(interval) .trim() .match(INTERVAL_STRING_RE); @@ -33,7 +33,7 @@ export function parseInterval(interval) { try { const value = parseFloat(matches[1]) || 1; - const unit = matches[2]; + const unit = matches[2] as unitOfTime.Base; const duration = moment.duration(value, unit); @@ -44,9 +44,10 @@ export function parseInterval(interval) { // adding 0.5 days until we hit the end date. However, since there is a bug in moment, when you add 0.5 days to // the start date, you get the same exact date (instead of being ahead by 12 hours). So instead of returning // a duration corresponding to 0.5 hours, we return a duration corresponding to 12 hours. - const selectedUnit = _.find(dateMath.units, function(unit) { - return Math.abs(duration.as(unit)) >= 1; - }); + const selectedUnit = find( + dateMath.units, + u => Math.abs(duration.as(u)) >= 1 + ) as unitOfTime.Base; return moment.duration(duration.as(selectedUnit), selectedUnit); } catch (e) { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 967887764237d2..4b330600417e7d 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -90,6 +90,8 @@ export { castEsToKbnFieldTypeName, getKbnFieldType, getKbnTypeNames, + // utils + parseInterval, } from '../common'; // Export plugin after all other imports diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index fe96c494bd9ff4..3cd088744a4391 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -18,7 +18,7 @@ */ import { PluginInitializerContext } from '../../../core/server'; -import { DataServerPlugin } from './plugin'; +import { DataServerPlugin, DataPluginSetup } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new DataServerPlugin(initializerContext); @@ -47,6 +47,8 @@ export { // timefilter RefreshInterval, TimeRange, + // utils + parseInterval, } from '../common'; /** @@ -91,4 +93,4 @@ export { getKbnTypeNames, } from '../common'; -export { DataServerPlugin as Plugin }; +export { DataServerPlugin as Plugin, DataPluginSetup as PluginSetup }; diff --git a/src/plugins/es_ui_shared/static/forms/components/field.tsx b/src/plugins/es_ui_shared/static/forms/components/field.tsx index 89dea53d75b38a..5b9a6dc9de002d 100644 --- a/src/plugins/es_ui_shared/static/forms/components/field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/field.tsx @@ -37,6 +37,7 @@ import { RadioGroupField, RangeField, SelectField, + SuperSelectField, ToggleField, } from './fields'; @@ -50,6 +51,7 @@ const mapTypeToFieldComponent = { [FIELD_TYPES.RADIO_GROUP]: RadioGroupField, [FIELD_TYPES.RANGE]: RangeField, [FIELD_TYPES.SELECT]: SelectField, + [FIELD_TYPES.SUPER_SELECT]: SuperSelectField, [FIELD_TYPES.TOGGLE]: ToggleField, }; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/index.ts b/src/plugins/es_ui_shared/static/forms/components/fields/index.ts index f973bb7b04d346..35635d0e8530c9 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/index.ts +++ b/src/plugins/es_ui_shared/static/forms/components/fields/index.ts @@ -25,5 +25,6 @@ export * from './multi_select_field'; export * from './radio_group_field'; export * from './range_field'; export * from './select_field'; +export * from './super_select_field'; export * from './toggle_field'; export * from './text_area_field'; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx new file mode 100644 index 00000000000000..9b29d75230d7a6 --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiFormRow, EuiSuperSelect } from '@elastic/eui'; + +import { FieldHook, getFieldValidityAndErrorMessage } from '../../hook_form_lib'; + +interface Props { + field: FieldHook; + euiFieldProps?: Record; + idAria?: string; + [key: string]: any; +} + +export const SuperSelectField = ({ field, euiFieldProps = {}, ...rest }: Props) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + return ( + + { + field.setValue(value); + }} + options={[]} + isInvalid={isInvalid} + data-test-subj="select" + {...euiFieldProps} + /> + + ); +}; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts index df2807e59ab465..4056947483107c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts @@ -28,6 +28,7 @@ export const FIELD_TYPES = { RADIO_GROUP: 'radioGroup', RANGE: 'range', SELECT: 'select', + SUPER_SELECT: 'superSelect', MULTI_SELECT: 'multiSelect', }; diff --git a/src/plugins/management/kibana.json b/src/plugins/management/kibana.json index 755a387afbd051..80135f1bfb6c84 100644 --- a/src/plugins/management/kibana.json +++ b/src/plugins/management/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": [] + "requiredPlugins": ["kibana_legacy"] } diff --git a/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap b/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap new file mode 100644 index 00000000000000..7f13472ee02eed --- /dev/null +++ b/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Management app can mount and unmount 1`] = ` +
+
+ Test App - Hello world! +
+
+`; + +exports[`Management app can mount and unmount 2`] = `
`; diff --git a/src/plugins/management/public/components/_index.scss b/src/plugins/management/public/components/_index.scss new file mode 100644 index 00000000000000..df0ebb48803d97 --- /dev/null +++ b/src/plugins/management/public/components/_index.scss @@ -0,0 +1 @@ +@import './management_sidebar_nav/index'; diff --git a/src/plugins/management/public/components/index.ts b/src/plugins/management/public/components/index.ts new file mode 100644 index 00000000000000..2650d23d3c25ca --- /dev/null +++ b/src/plugins/management/public/components/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ManagementSidebarNav } from './management_sidebar_nav'; +export { ManagementChrome } from './management_chrome'; diff --git a/src/legacy/ui/public/management/components/index.ts b/src/plugins/management/public/components/management_chrome/index.ts similarity index 93% rename from src/legacy/ui/public/management/components/index.ts rename to src/plugins/management/public/components/management_chrome/index.ts index e3a18ec4e2698f..b82c1af871be71 100644 --- a/src/legacy/ui/public/management/components/index.ts +++ b/src/plugins/management/public/components/management_chrome/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { SidebarNav } from './sidebar_nav'; +export { ManagementChrome } from './management_chrome'; diff --git a/src/plugins/management/public/components/management_chrome/management_chrome.tsx b/src/plugins/management/public/components/management_chrome/management_chrome.tsx new file mode 100644 index 00000000000000..7e5cabd32e48f8 --- /dev/null +++ b/src/plugins/management/public/components/management_chrome/management_chrome.tsx @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiPage, EuiPageBody } from '@elastic/eui'; +import { I18nProvider } from '@kbn/i18n/react'; +import { ManagementSidebarNav } from '../management_sidebar_nav'; +import { LegacySection } from '../../types'; +import { ManagementSection } from '../../management_section'; + +interface Props { + getSections: () => ManagementSection[]; + legacySections: LegacySection[]; + selectedId: string; + onMounted: (element: HTMLDivElement) => void; +} + +export class ManagementChrome extends React.Component { + private container = React.createRef(); + componentDidMount() { + if (this.container.current) { + this.props.onMounted(this.container.current); + } + } + render() { + return ( + + + + +
+ + + + ); + } +} diff --git a/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap b/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap new file mode 100644 index 00000000000000..e7225b356ed685 --- /dev/null +++ b/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Management adds legacy apps to existing SidebarNav sections 1`] = ` +Array [ + Object { + "data-test-subj": "activeSection", + "icon": null, + "id": "activeSection", + "items": Array [ + Object { + "data-test-subj": "item", + "href": undefined, + "id": "item", + "isSelected": false, + "name": "item", + "order": undefined, + }, + ], + "name": "activeSection", + "order": 10, + }, + Object { + "data-test-subj": "no-active-items", + "icon": null, + "id": "no-active-items", + "items": Array [ + Object { + "data-test-subj": "disabled", + "href": undefined, + "id": "disabled", + "isSelected": false, + "name": "disabled", + "order": undefined, + }, + Object { + "data-test-subj": "notVisible", + "href": undefined, + "id": "notVisible", + "isSelected": false, + "name": "notVisible", + "order": undefined, + }, + ], + "name": "No active items", + "order": 10, + }, +] +`; + +exports[`Management maps legacy sections and apps into SidebarNav items 1`] = ` +Array [ + Object { + "data-test-subj": "no-active-items", + "icon": null, + "id": "no-active-items", + "items": Array [ + Object { + "data-test-subj": "disabled", + "href": undefined, + "id": "disabled", + "isSelected": false, + "name": "disabled", + "order": undefined, + }, + Object { + "data-test-subj": "notVisible", + "href": undefined, + "id": "notVisible", + "isSelected": false, + "name": "notVisible", + "order": undefined, + }, + ], + "name": "No active items", + "order": 10, + }, + Object { + "data-test-subj": "activeSection", + "icon": null, + "id": "activeSection", + "items": Array [ + Object { + "data-test-subj": "item", + "href": undefined, + "id": "item", + "isSelected": false, + "name": "item", + "order": undefined, + }, + ], + "name": "activeSection", + "order": 10, + }, +] +`; diff --git a/src/legacy/ui/public/management/components/_index.scss b/src/plugins/management/public/components/management_sidebar_nav/_index.scss similarity index 100% rename from src/legacy/ui/public/management/components/_index.scss rename to src/plugins/management/public/components/management_sidebar_nav/_index.scss diff --git a/src/legacy/ui/public/management/components/_sidebar_nav.scss b/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss similarity index 88% rename from src/legacy/ui/public/management/components/_sidebar_nav.scss rename to src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss index 0c2b2bc228b2cc..cf88ed9b0a88b5 100644 --- a/src/legacy/ui/public/management/components/_sidebar_nav.scss +++ b/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss @@ -1,4 +1,4 @@ -.mgtSidebarNav { +.mgtSideBarNav { width: 192px; } diff --git a/src/legacy/ui/public/utils/parse_interval.d.ts b/src/plugins/management/public/components/management_sidebar_nav/index.ts similarity index 88% rename from src/legacy/ui/public/utils/parse_interval.d.ts rename to src/plugins/management/public/components/management_sidebar_nav/index.ts index 9d78b4ef6cddfe..79142fdb69a74d 100644 --- a/src/legacy/ui/public/utils/parse_interval.d.ts +++ b/src/plugins/management/public/components/management_sidebar_nav/index.ts @@ -17,6 +17,4 @@ * under the License. */ -import moment from 'moment'; - -export function parseInterval(interval: string): moment.Duration | null; +export { ManagementSidebarNav } from './management_sidebar_nav'; diff --git a/src/legacy/ui/public/management/components/sidebar_nav.test.ts b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts similarity index 75% rename from src/legacy/ui/public/management/components/sidebar_nav.test.ts rename to src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts index e02cc7d2901b64..e04e0a75726126 100644 --- a/src/legacy/ui/public/management/components/sidebar_nav.test.ts +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts @@ -17,8 +17,8 @@ * under the License. */ -import { IndexedArray } from '../../indexed_array'; -import { sideNavItems } from '../components/sidebar_nav'; +import { IndexedArray } from '../../../../../legacy/ui/public/indexed_array'; +import { mergeLegacyItems } from './management_sidebar_nav'; const toIndexedArray = (initialSet: any[]) => new IndexedArray({ @@ -30,30 +30,33 @@ const toIndexedArray = (initialSet: any[]) => const activeProps = { visible: true, disabled: false }; const disabledProps = { visible: true, disabled: true }; const notVisibleProps = { visible: false, disabled: false }; - const visibleItem = { display: 'item', id: 'item', ...activeProps }; const notVisibleSection = { display: 'Not visible', id: 'not-visible', + order: 10, visibleItems: toIndexedArray([visibleItem]), ...notVisibleProps, }; const disabledSection = { display: 'Disabled', id: 'disabled', + order: 10, visibleItems: toIndexedArray([visibleItem]), ...disabledProps, }; const noItemsSection = { display: 'No items', id: 'no-items', + order: 10, visibleItems: toIndexedArray([]), ...activeProps, }; const noActiveItemsSection = { display: 'No active items', id: 'no-active-items', + order: 10, visibleItems: toIndexedArray([ { display: 'disabled', id: 'disabled', ...disabledProps }, { display: 'notVisible', id: 'notVisible', ...notVisibleProps }, @@ -63,6 +66,7 @@ const noActiveItemsSection = { const activeSection = { display: 'activeSection', id: 'activeSection', + order: 10, visibleItems: toIndexedArray([visibleItem]), ...activeProps, }; @@ -76,7 +80,19 @@ const managementSections = [ ]; describe('Management', () => { - it('filters and filters and maps section objects into SidebarNav items', () => { - expect(sideNavItems(managementSections, 'active-item-id')).toMatchSnapshot(); + it('maps legacy sections and apps into SidebarNav items', () => { + expect(mergeLegacyItems([], managementSections, 'active-item-id')).toMatchSnapshot(); + }); + + it('adds legacy apps to existing SidebarNav sections', () => { + const navSection = { + 'data-test-subj': 'activeSection', + icon: null, + id: 'activeSection', + items: [], + name: 'activeSection', + order: 10, + }; + expect(mergeLegacyItems([navSection], managementSections, 'active-item-id')).toMatchSnapshot(); }); }); diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx new file mode 100644 index 00000000000000..cb0b82d0f0bde4 --- /dev/null +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -0,0 +1,200 @@ +/* + * 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 { + EuiIcon, + // @ts-ignore + EuiSideNav, + EuiScreenReaderOnly, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { LegacySection, LegacyApp } from '../../types'; +import { ManagementApp } from '../../management_app'; +import { ManagementSection } from '../../management_section'; + +interface NavApp { + id: string; + name: string; + [key: string]: unknown; + order: number; // only needed while merging platform and legacy +} + +interface NavSection extends NavApp { + items: NavApp[]; +} + +interface ManagementSidebarNavProps { + getSections: () => ManagementSection[]; + legacySections: LegacySection[]; + selectedId: string; +} + +interface ManagementSidebarNavState { + isSideNavOpenOnMobile: boolean; +} + +const managementSectionOrAppToNav = (appOrSection: ManagementApp | ManagementSection) => ({ + id: appOrSection.id, + name: appOrSection.title, + 'data-test-subj': appOrSection.id, + order: appOrSection.order, +}); + +const managementSectionToNavSection = (section: ManagementSection) => { + const iconType = section.euiIconType + ? section.euiIconType + : section.icon + ? section.icon + : 'empty'; + + return { + icon: , + ...managementSectionOrAppToNav(section), + }; +}; + +const managementAppToNavItem = (selectedId?: string, parentId?: string) => ( + app: ManagementApp +) => ({ + isSelected: selectedId === app.id, + href: `#/management/${parentId}/${app.id}`, + ...managementSectionOrAppToNav(app), +}); + +const legacySectionToNavSection = (section: LegacySection) => ({ + name: section.display, + id: section.id, + icon: section.icon ? : null, + items: [], + 'data-test-subj': section.id, + // @ts-ignore + order: section.order, +}); + +const legacyAppToNavItem = (app: LegacyApp, selectedId: string) => ({ + isSelected: selectedId === app.id, + name: app.display, + id: app.id, + href: app.url, + 'data-test-subj': app.id, + // @ts-ignore + order: app.order, +}); + +const sectionVisible = (section: LegacySection | LegacyApp) => !section.disabled && section.visible; + +const sideNavItems = (sections: ManagementSection[], selectedId: string) => + sections.map(section => ({ + items: section.getAppsEnabled().map(managementAppToNavItem(selectedId, section.id)), + ...managementSectionToNavSection(section), + })); + +const findOrAddSection = (navItems: NavSection[], legacySection: LegacySection): NavSection => { + const foundSection = navItems.find(sec => sec.id === legacySection.id); + + if (foundSection) { + return foundSection; + } else { + const newSection = legacySectionToNavSection(legacySection); + navItems.push(newSection); + navItems.sort((a: NavSection, b: NavSection) => a.order - b.order); // only needed while merging platform and legacy + return newSection; + } +}; + +export const mergeLegacyItems = ( + navItems: NavSection[], + legacySections: LegacySection[], + selectedId: string +) => { + const filteredLegacySections = legacySections + .filter(sectionVisible) + .filter(section => section.visibleItems.length); + + filteredLegacySections.forEach(legacySection => { + const section = findOrAddSection(navItems, legacySection); + legacySection.visibleItems.forEach(app => { + section.items.push(legacyAppToNavItem(app, selectedId)); + return section.items.sort((a, b) => a.order - b.order); + }); + }); + + return navItems; +}; + +const sectionsToItems = ( + sections: ManagementSection[], + legacySections: LegacySection[], + selectedId: string +) => { + const navItems = sideNavItems(sections, selectedId); + return mergeLegacyItems(navItems, legacySections, selectedId); +}; + +export class ManagementSidebarNav extends React.Component< + ManagementSidebarNavProps, + ManagementSidebarNavState +> { + constructor(props: ManagementSidebarNavProps) { + super(props); + this.state = { + isSideNavOpenOnMobile: false, + }; + } + + public render() { + const HEADER_ID = 'management-nav-header'; + + return ( + <> + +

+ {i18n.translate('management.nav.label', { + defaultMessage: 'Management', + })} +

+
+ + + ); + } + + private renderMobileTitle() { + return ; + } + + private toggleOpenOnMobile = () => { + this.setState({ + isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile, + }); + }; +} diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index ee3866c734f194..faec466dbd6714 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -24,4 +24,7 @@ export function plugin(initializerContext: PluginInitializerContext) { return new ManagementPlugin(); } -export { ManagementStart } from './types'; +export { ManagementSetup, ManagementStart, RegisterManagementApp } from './types'; +export { ManagementApp } from './management_app'; +export { ManagementSection } from './management_section'; +export { ManagementSidebarNav } from './components'; // for use in legacy management apps diff --git a/src/plugins/management/public/legacy/index.js b/src/plugins/management/public/legacy/index.js index 63b9d2c6b27d7e..f2e0ba89b7b597 100644 --- a/src/plugins/management/public/legacy/index.js +++ b/src/plugins/management/public/legacy/index.js @@ -17,4 +17,5 @@ * under the License. */ -export { management } from './sections_register'; +export { LegacyManagementAdapter } from './sections_register'; +export { LegacyManagementSection } from './section'; diff --git a/src/plugins/management/public/legacy/section.js b/src/plugins/management/public/legacy/section.js index f269e3fe295b75..7d733b7b3173bf 100644 --- a/src/plugins/management/public/legacy/section.js +++ b/src/plugins/management/public/legacy/section.js @@ -22,7 +22,7 @@ import { IndexedArray } from '../../../../legacy/ui/public/indexed_array'; const listeners = []; -export class ManagementSection { +export class LegacyManagementSection { /** * @param {string} id * @param {object} options @@ -83,7 +83,11 @@ export class ManagementSection { */ register(id, options = {}) { - const item = new ManagementSection(id, assign(options, { parent: this }), this.capabilities); + const item = new LegacyManagementSection( + id, + assign(options, { parent: this }), + this.capabilities + ); if (this.hasItem(id)) { throw new Error(`'${id}' is already registered`); diff --git a/src/plugins/management/public/legacy/section.test.js b/src/plugins/management/public/legacy/section.test.js index 61bafd298afb32..45cc80ef80edd1 100644 --- a/src/plugins/management/public/legacy/section.test.js +++ b/src/plugins/management/public/legacy/section.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { ManagementSection } from './section'; +import { LegacyManagementSection } from './section'; import { IndexedArray } from '../../../../legacy/ui/public/indexed_array'; const capabilitiesMock = { @@ -29,42 +29,42 @@ const capabilitiesMock = { describe('ManagementSection', () => { describe('constructor', () => { it('defaults display to id', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.display).toBe('kibana'); }); it('defaults visible to true', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.visible).toBe(true); }); it('defaults disabled to false', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.disabled).toBe(false); }); it('defaults tooltip to empty string', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.tooltip).toBe(''); }); it('defaults url to empty string', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.url).toBe(''); }); it('exposes items', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.items).toHaveLength(0); }); it('exposes visibleItems', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.visibleItems).toHaveLength(0); }); it('assigns all options', () => { - const section = new ManagementSection( + const section = new LegacyManagementSection( 'kibana', { description: 'test', url: 'foobar' }, capabilitiesMock @@ -78,11 +78,11 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); }); it('returns a ManagementSection', () => { - expect(section.register('about')).toBeInstanceOf(ManagementSection); + expect(section.register('about')).toBeInstanceOf(LegacyManagementSection); }); it('provides a reference to the parent', () => { @@ -93,7 +93,7 @@ describe('ManagementSection', () => { section.register('about', { description: 'test' }); expect(section.items).toHaveLength(1); - expect(section.items[0]).toBeInstanceOf(ManagementSection); + expect(section.items[0]).toBeInstanceOf(LegacyManagementSection); expect(section.items[0].id).toBe('about'); }); @@ -126,7 +126,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('about'); }); @@ -157,12 +157,12 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('about'); }); it('returns registered section', () => { - expect(section.getSection('about')).toBeInstanceOf(ManagementSection); + expect(section.getSection('about')).toBeInstanceOf(LegacyManagementSection); }); it('returns undefined if un-registered', () => { @@ -171,7 +171,7 @@ describe('ManagementSection', () => { it('returns sub-sections specified via a /-separated path', () => { section.getSection('about').register('time'); - expect(section.getSection('about/time')).toBeInstanceOf(ManagementSection); + expect(section.getSection('about/time')).toBeInstanceOf(LegacyManagementSection); expect(section.getSection('about/time')).toBe(section.getSection('about').getSection('time')); }); @@ -184,7 +184,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('three', { order: 3 }); section.register('one', { order: 1 }); @@ -214,7 +214,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); }); it('hide sets visible to false', () => { @@ -233,7 +233,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); }); it('disable sets disabled to true', () => { @@ -251,7 +251,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('three', { order: 3 }); section.register('one', { order: 1 }); diff --git a/src/plugins/management/public/legacy/sections_register.js b/src/plugins/management/public/legacy/sections_register.js index 888b2c5bc3aeb6..63d919377f89ea 100644 --- a/src/plugins/management/public/legacy/sections_register.js +++ b/src/plugins/management/public/legacy/sections_register.js @@ -17,44 +17,48 @@ * under the License. */ -import { ManagementSection } from './section'; +import { LegacyManagementSection } from './section'; import { i18n } from '@kbn/i18n'; -export const management = capabilities => { - const main = new ManagementSection( - 'management', - { - display: i18n.translate('management.displayName', { - defaultMessage: 'Management', - }), - }, - capabilities - ); +export class LegacyManagementAdapter { + main = undefined; + init = capabilities => { + this.main = new LegacyManagementSection( + 'management', + { + display: i18n.translate('management.displayName', { + defaultMessage: 'Management', + }), + }, + capabilities + ); - main.register('data', { - display: i18n.translate('management.connectDataDisplayName', { - defaultMessage: 'Connect Data', - }), - order: 0, - }); + this.main.register('data', { + display: i18n.translate('management.connectDataDisplayName', { + defaultMessage: 'Connect Data', + }), + order: 0, + }); - main.register('elasticsearch', { - display: 'Elasticsearch', - order: 20, - icon: 'logoElasticsearch', - }); + this.main.register('elasticsearch', { + display: 'Elasticsearch', + order: 20, + icon: 'logoElasticsearch', + }); - main.register('kibana', { - display: 'Kibana', - order: 30, - icon: 'logoKibana', - }); + this.main.register('kibana', { + display: 'Kibana', + order: 30, + icon: 'logoKibana', + }); - main.register('logstash', { - display: 'Logstash', - order: 30, - icon: 'logoLogstash', - }); + this.main.register('logstash', { + display: 'Logstash', + order: 30, + icon: 'logoLogstash', + }); - return main; -}; + return this.main; + }; + getManagement = () => this.main; +} diff --git a/src/plugins/management/public/management_app.test.tsx b/src/plugins/management/public/management_app.test.tsx new file mode 100644 index 00000000000000..a76b234d95ef51 --- /dev/null +++ b/src/plugins/management/public/management_app.test.tsx @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { coreMock } from '../../../core/public/mocks'; + +import { ManagementApp } from './management_app'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; + +function createTestApp() { + const legacySection = new LegacyManagementSection('legacy'); + return new ManagementApp( + { + id: 'test-app', + title: 'Test App', + basePath: '', + mount(params) { + params.setBreadcrumbs([{ text: 'Test App' }]); + ReactDOM.render(
Test App - Hello world!
, params.element); + + return () => { + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }, + () => [], + jest.fn(), + () => legacySection, + coreMock.createSetup().getStartServices + ); +} + +test('Management app can mount and unmount', async () => { + const testApp = createTestApp(); + const container = document.createElement('div'); + document.body.appendChild(container); + const unmount = testApp.mount({ element: container, basePath: '', setBreadcrumbs: jest.fn() }); + expect(container).toMatchSnapshot(); + (await unmount)(); + expect(container).toMatchSnapshot(); +}); + +test('Enabled by default, can disable', () => { + const testApp = createTestApp(); + expect(testApp.enabled).toBe(true); + testApp.disable(); + expect(testApp.enabled).toBe(false); +}); diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx new file mode 100644 index 00000000000000..f7e8dba4f82100 --- /dev/null +++ b/src/plugins/management/public/management_app.tsx @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { CreateManagementApp, ManagementSectionMount, Unmount } from './types'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { ManagementChrome } from './components'; +import { ManagementSection } from './management_section'; +import { ChromeBreadcrumb, CoreSetup } from '../../../core/public/'; + +export class ManagementApp { + readonly id: string; + readonly title: string; + readonly basePath: string; + readonly order: number; + readonly mount: ManagementSectionMount; + protected enabledStatus: boolean = true; + + constructor( + { id, title, basePath, order = 100, mount }: CreateManagementApp, + getSections: () => ManagementSection[], + registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], + getLegacyManagementSections: () => LegacyManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + this.id = id; + this.title = title; + this.basePath = basePath; + this.order = order; + this.mount = mount; + + registerLegacyApp({ + id: basePath.substr(1), // get rid of initial slash + title, + mount: async ({}, params) => { + let appUnmount: Unmount; + async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) { + const [coreStart] = await getStartServices(); + coreStart.chrome.setBreadcrumbs([ + { + text: i18n.translate('management.breadcrumb', { + defaultMessage: 'Management', + }), + href: '#/management', + }, + ...crumbs, + ]); + } + + ReactDOM.render( + { + appUnmount = await mount({ + basePath, + element, + setBreadcrumbs, + }); + }} + />, + params.element + ); + + return async () => { + appUnmount(); + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }); + } + public enable() { + this.enabledStatus = true; + } + public disable() { + this.enabledStatus = false; + } + public get enabled() { + return this.enabledStatus; + } +} diff --git a/src/plugins/management/public/management_section.test.ts b/src/plugins/management/public/management_section.test.ts new file mode 100644 index 00000000000000..c68175ee0a678b --- /dev/null +++ b/src/plugins/management/public/management_section.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { ManagementSection } from './management_section'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { coreMock } from '../../../core/public/mocks'; + +function createSection(registerLegacyApp: () => void) { + const legacySection = new LegacyManagementSection('legacy'); + const getLegacySection = () => legacySection; + const getManagementSections: () => ManagementSection[] = () => []; + + const testSectionConfig = { id: 'test-section', title: 'Test Section' }; + return new ManagementSection( + testSectionConfig, + getManagementSections, + registerLegacyApp, + getLegacySection, + coreMock.createSetup().getStartServices + ); +} + +test('cannot register two apps with the same id', () => { + const registerLegacyApp = jest.fn(); + const section = createSection(registerLegacyApp); + + const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; + + section.registerApp(testAppConfig); + expect(registerLegacyApp).toHaveBeenCalled(); + expect(section.apps.length).toEqual(1); + + expect(() => { + section.registerApp(testAppConfig); + }).toThrow(); +}); + +test('can enable and disable apps', () => { + const registerLegacyApp = jest.fn(); + const section = createSection(registerLegacyApp); + + const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; + + const app = section.registerApp(testAppConfig); + expect(section.getAppsEnabled().length).toEqual(1); + app.disable(); + expect(section.getAppsEnabled().length).toEqual(0); +}); diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts new file mode 100644 index 00000000000000..2f323c4b6a9cf3 --- /dev/null +++ b/src/plugins/management/public/management_section.ts @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CreateSection, RegisterManagementAppArgs } from './types'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; +import { CoreSetup } from '../../../core/public'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { ManagementApp } from './management_app'; + +export class ManagementSection { + public readonly id: string = ''; + public readonly title: string = ''; + public readonly apps: ManagementApp[] = []; + public readonly order: number; + public readonly euiIconType?: string; + public readonly icon?: string; + private readonly getSections: () => ManagementSection[]; + private readonly registerLegacyApp: KibanaLegacySetup['registerLegacyApp']; + private readonly getLegacyManagementSection: () => LegacyManagementSection; + private readonly getStartServices: CoreSetup['getStartServices']; + + constructor( + { id, title, order = 100, euiIconType, icon }: CreateSection, + getSections: () => ManagementSection[], + registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], + getLegacyManagementSection: () => ManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + this.id = id; + this.title = title; + this.order = order; + this.euiIconType = euiIconType; + this.icon = icon; + this.getSections = getSections; + this.registerLegacyApp = registerLegacyApp; + this.getLegacyManagementSection = getLegacyManagementSection; + this.getStartServices = getStartServices; + } + + registerApp({ id, title, order, mount }: RegisterManagementAppArgs) { + if (this.getApp(id)) { + throw new Error(`Management app already registered - id: ${id}, title: ${title}`); + } + + const app = new ManagementApp( + { id, title, order, mount, basePath: `/management/${this.id}/${id}` }, + this.getSections, + this.registerLegacyApp, + this.getLegacyManagementSection, + this.getStartServices + ); + this.apps.push(app); + return app; + } + getApp(id: ManagementApp['id']) { + return this.apps.find(app => app.id === id); + } + getAppsEnabled() { + return this.apps.filter(app => app.enabled).sort((a, b) => a.order - b.order); + } +} diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts new file mode 100644 index 00000000000000..854406a10335be --- /dev/null +++ b/src/plugins/management/public/management_service.test.ts @@ -0,0 +1,55 @@ +/* + * 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 { ManagementService } from './management_service'; +import { coreMock } from '../../../core/public/mocks'; + +const mockKibanaLegacy = { registerLegacyApp: () => {}, forwardApp: () => {} }; + +test('Provides default sections', () => { + const service = new ManagementService().setup( + mockKibanaLegacy, + () => {}, + coreMock.createSetup().getStartServices + ); + expect(service.getAllSections().length).toEqual(3); + expect(service.getSection('kibana')).not.toBeUndefined(); + expect(service.getSection('logstash')).not.toBeUndefined(); + expect(service.getSection('elasticsearch')).not.toBeUndefined(); +}); + +test('Register section, enable and disable', () => { + const service = new ManagementService().setup( + mockKibanaLegacy, + () => {}, + coreMock.createSetup().getStartServices + ); + const testSection = service.register({ id: 'test-section', title: 'Test Section' }); + expect(service.getSection('test-section')).not.toBeUndefined(); + + const testApp = testSection.registerApp({ + id: 'test-app', + title: 'Test App', + mount: () => () => {}, + }); + expect(testSection.getApp('test-app')).not.toBeUndefined(); + expect(service.getSectionsEnabled().length).toEqual(1); + testApp.disable(); + expect(service.getSectionsEnabled().length).toEqual(0); +}); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts new file mode 100644 index 00000000000000..4a900345b38436 --- /dev/null +++ b/src/plugins/management/public/management_service.ts @@ -0,0 +1,103 @@ +/* + * 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 { ManagementSection } from './management_section'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { CreateSection } from './types'; +import { CoreSetup, CoreStart } from '../../../core/public'; + +export class ManagementService { + private sections: ManagementSection[] = []; + + private register( + registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], + getLegacyManagement: () => LegacyManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + return (section: CreateSection) => { + if (this.getSection(section.id)) { + throw Error(`ManagementSection '${section.id}' already registered`); + } + + const newSection = new ManagementSection( + section, + this.getSectionsEnabled.bind(this), + registerLegacyApp, + getLegacyManagement, + getStartServices + ); + this.sections.push(newSection); + return newSection; + }; + } + private getSection(sectionId: ManagementSection['id']) { + return this.sections.find(section => section.id === sectionId); + } + + private getAllSections() { + return this.sections; + } + + private getSectionsEnabled() { + return this.sections + .filter(section => section.getAppsEnabled().length > 0) + .sort((a, b) => a.order - b.order); + } + + private sharedInterface = { + getSection: this.getSection.bind(this), + getSectionsEnabled: this.getSectionsEnabled.bind(this), + getAllSections: this.getAllSections.bind(this), + }; + + public setup( + kibanaLegacy: KibanaLegacySetup, + getLegacyManagement: () => LegacyManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + const register = this.register.bind(this)( + kibanaLegacy.registerLegacyApp, + getLegacyManagement, + getStartServices + ); + + register({ id: 'kibana', title: 'Kibana', order: 30, euiIconType: 'logoKibana' }); + register({ id: 'logstash', title: 'Logstash', order: 30, euiIconType: 'logoLogstash' }); + register({ + id: 'elasticsearch', + title: 'Elasticsearch', + order: 20, + euiIconType: 'logoElasticsearch', + }); + + return { + register, + ...this.sharedInterface, + }; + } + + public start(navigateToApp: CoreStart['application']['navigateToApp']) { + return { + navigateToApp, // apps are currently registered as top level apps but this may change in the future + ...this.sharedInterface, + }; + } +} diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index c65dfd1dc7bb45..195d96c11d8d92 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -18,18 +18,30 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { ManagementStart } from './types'; +import { ManagementSetup, ManagementStart } from './types'; +import { ManagementService } from './management_service'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; // @ts-ignore -import { management } from './legacy'; +import { LegacyManagementAdapter } from './legacy'; -export class ManagementPlugin implements Plugin<{}, ManagementStart> { - public setup(core: CoreSetup) { - return {}; +export class ManagementPlugin implements Plugin { + private managementSections = new ManagementService(); + private legacyManagement = new LegacyManagementAdapter(); + + public setup(core: CoreSetup, { kibana_legacy }: { kibana_legacy: KibanaLegacySetup }) { + return { + sections: this.managementSections.setup( + kibana_legacy, + this.legacyManagement.getManagement, + core.getStartServices + ), + }; } public start(core: CoreStart) { return { - legacy: management(core.application.capabilities), + sections: this.managementSections.start(core.application.navigateToApp), + legacy: this.legacyManagement.init(core.application.capabilities), }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 6ca1faf338c39f..4dbea30ff062da 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -17,6 +17,82 @@ * under the License. */ +import { IconType } from '@elastic/eui'; +import { ManagementApp } from './management_app'; +import { ManagementSection } from './management_section'; +import { ChromeBreadcrumb, ApplicationStart } from '../../../core/public/'; + +export interface ManagementSetup { + sections: SectionsServiceSetup; +} + export interface ManagementStart { + sections: SectionsServiceStart; legacy: any; } + +interface SectionsServiceSetup { + getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getAllSections: () => ManagementSection[]; + register: RegisterSection; +} + +interface SectionsServiceStart { + getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getAllSections: () => ManagementSection[]; + navigateToApp: ApplicationStart['navigateToApp']; +} + +export interface CreateSection { + id: string; + title: string; + order?: number; + euiIconType?: string; // takes precedence over `icon` property. + icon?: string; // URL to image file; fallback if no `euiIconType` +} + +export type RegisterSection = (section: CreateSection) => ManagementSection; + +export interface RegisterManagementAppArgs { + id: string; + title: string; + mount: ManagementSectionMount; + order?: number; +} + +export type RegisterManagementApp = (managementApp: RegisterManagementAppArgs) => ManagementApp; + +export type Unmount = () => Promise | void; + +interface ManagementAppMountParams { + basePath: string; // base path for setting up your router + element: HTMLElement; // element the section should render into + setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; +} + +export type ManagementSectionMount = ( + params: ManagementAppMountParams +) => Unmount | Promise; + +export interface CreateManagementApp { + id: string; + title: string; + basePath: string; + order?: number; + mount: ManagementSectionMount; +} + +export interface LegacySection extends LegacyApp { + visibleItems: LegacyApp[]; +} + +export interface LegacyApp { + disabled: boolean; + visible: boolean; + id: string; + display: string; + url?: string; + euiIconType?: IconType; + icon?: string; + order: number; +} diff --git a/src/plugins/testbed/server/index.ts b/src/plugins/testbed/server/index.ts index 586254603567bb..4873fe0926472b 100644 --- a/src/plugins/testbed/server/index.ts +++ b/src/plugins/testbed/server/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { map, mergeMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { schema, TypeOf } from '@kbn/config-schema'; import { @@ -108,9 +108,7 @@ class Plugin { return `Some exposed data derived from config: ${configValue.secret}`; }) ), - pingElasticsearch$: core.elasticsearch.adminClient$.pipe( - mergeMap(client => client.callAsInternalUser('ping')) - ), + pingElasticsearch: () => core.elasticsearch.adminClient.callAsInternalUser('ping'), }; } diff --git a/src/test_utils/public/simulate_keys.js b/src/test_utils/public/simulate_keys.js index 8ee4a79e6bc7c4..a876d67325c053 100644 --- a/src/test_utils/public/simulate_keys.js +++ b/src/test_utils/public/simulate_keys.js @@ -20,7 +20,7 @@ import $ from 'jquery'; import _ from 'lodash'; import Bluebird from 'bluebird'; -import { keyMap } from 'ui/utils/key_map'; +import { keyMap } from 'ui/directives/key_map'; const reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1)); /** diff --git a/tasks/config/run.js b/tasks/config/run.js index a29061c9a72405..857895d75595cb 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -152,6 +152,7 @@ module.exports = function(grunt) { args: [ 'nyc', '--reporter=html', + '--reporter=json-summary', '--report-dir=./target/kibana-coverage/mocha', NODE, 'scripts/mocha', diff --git a/tasks/function_test_groups.js b/tasks/function_test_groups.js index 7854e2cd49837b..7b7293dc9a037d 100644 --- a/tasks/function_test_groups.js +++ b/tasks/function_test_groups.js @@ -29,6 +29,21 @@ const TEST_TAGS = safeLoad(JOBS_YAML) .JOB.filter(id => id.startsWith('kibana-ciGroup')) .map(id => id.replace(/^kibana-/, '')); +const getDefaultArgs = tag => { + return [ + 'scripts/functional_tests', + '--include-tag', + tag, + '--config', + 'test/functional/config.js', + '--config', + 'test/ui_capabilities/newsfeed_err/config.ts', + // '--config', 'test/functional/config.firefox.js', + '--bail', + '--debug', + ]; +}; + export function getFunctionalTestGroupRunConfigs({ kibanaInstallDir } = {}) { return { // include a run task for each test group @@ -38,18 +53,8 @@ export function getFunctionalTestGroupRunConfigs({ kibanaInstallDir } = {}) { [`functionalTests_${tag}`]: { cmd: process.execPath, args: [ - 'scripts/functional_tests', - '--include-tag', - tag, - '--config', - 'test/functional/config.js', - '--config', - 'test/ui_capabilities/newsfeed_err/config.ts', - // '--config', 'test/functional/config.firefox.js', - '--bail', - '--debug', - '--kibana-install-dir', - kibanaInstallDir, + ...getDefaultArgs(tag), + ...(!!process.env.CODE_COVERAGE ? [] : ['--kibana-install-dir', kibanaInstallDir]), ], }, }), diff --git a/test/functional/apps/discover/_discover_histogram.js b/test/functional/apps/discover/_discover_histogram.js new file mode 100644 index 00000000000000..4780f36fc27c6e --- /dev/null +++ b/test/functional/apps/discover/_discover_histogram.js @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; + +export default function({ getService, getPageObjects }) { + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); + const defaultSettings = { + defaultIndex: 'long-window-logstash-*', + 'dateFormat:tz': 'Europe/Berlin', + }; + + describe('discover histogram', function describeIndexTests() { + before(async function() { + log.debug('load kibana index with default index pattern'); + await PageObjects.common.navigateToApp('home'); + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('long_window_logstash'); + await esArchiver.load('visualize'); + await esArchiver.load('discover'); + + log.debug('create long_window_logstash index pattern'); + // NOTE: long_window_logstash load does NOT create index pattern + await PageObjects.settings.createIndexPattern('long-window-logstash-'); + await kibanaServer.uiSettings.replace(defaultSettings); + await browser.refresh(); + + log.debug('discover'); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('long-window-logstash-*'); + // NOTE: For some reason without setting this relative time, the abs times will not fetch data. + await PageObjects.timePicker.setCommonlyUsedTime('superDatePickerCommonlyUsed_Last_1 year'); + }); + after(async () => { + await esArchiver.unload('long_window_logstash'); + await esArchiver.unload('visualize'); + await esArchiver.unload('discover'); + }); + + it('should visualize monthly data with different day intervals', async () => { + //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 + const fromTime = '2017-11-01 00:00:00.000'; + const toTime = '2018-03-21 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.setChartInterval('Monthly'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const chartCanvasExist = await PageObjects.discover.chartCanvasExist(); + expect(chartCanvasExist).to.be(true); + }); + it('should visualize weekly data with within DST changes', async () => { + //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 + const fromTime = '2018-03-01 00:00:00.000'; + const toTime = '2018-05-01 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.setChartInterval('Weekly'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const chartCanvasExist = await PageObjects.discover.chartCanvasExist(); + expect(chartCanvasExist).to.be(true); + }); + it('should visualize monthly data with different years Scaled to 30d', async () => { + //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 + const fromTime = '2010-01-01 00:00:00.000'; + const toTime = '2018-03-21 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.setChartInterval('Daily'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const chartCanvasExist = await PageObjects.discover.chartCanvasExist(); + expect(chartCanvasExist).to.be(true); + }); + }); +} diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index e10e772e93ab1c..64a5a61335365e 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -34,6 +34,7 @@ export default function({ getService, loadTestFile }) { loadTestFile(require.resolve('./_saved_queries')); loadTestFile(require.resolve('./_discover')); + loadTestFile(require.resolve('./_discover_histogram')); loadTestFile(require.resolve('./_filter_editor')); loadTestFile(require.resolve('./_errors')); loadTestFile(require.resolve('./_field_data')); diff --git a/test/functional/apps/management/_handle_alias.js b/test/functional/apps/management/_handle_alias.js index 06406bddeb009d..3d9368f8d46807 100644 --- a/test/functional/apps/management/_handle_alias.js +++ b/test/functional/apps/management/_handle_alias.js @@ -74,7 +74,7 @@ export default function({ getService, getPageObjects }) { const toTime = 'Nov 19, 2016 @ 05:00:00.000'; await PageObjects.common.navigateToApp('discover'); - await PageObjects.discover.selectIndexPattern('alias2'); + await PageObjects.discover.selectIndexPattern('alias2*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await retry.try(async function() { diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index 7029fbf9e1350b..3ba0f217813f2c 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -117,8 +117,16 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { await testSubjects.click('discoverOpenButton'); } + async getChartCanvas() { + return await find.byCssSelector('.echChart canvas:last-of-type'); + } + + async chartCanvasExist() { + return await find.existsByCssSelector('.echChart canvas:last-of-type'); + } + async clickHistogramBar() { - const el = await find.byCssSelector('.echChart canvas:last-of-type'); + const el = await this.getChartCanvas(); await browser .getActions() @@ -128,7 +136,8 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { } async brushHistogram() { - const el = await find.byCssSelector('.echChart canvas:last-of-type'); + const el = await this.getChartCanvas(); + await browser.dragAndDrop( { location: el, offset: { x: 200, y: 20 } }, { location: el, offset: { x: 400, y: 30 } } @@ -279,7 +288,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { async selectIndexPattern(indexPattern) { await testSubjects.click('indexPattern-switch-link'); await find.clickByCssSelector( - `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}*"]` + `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]` ); await PageObjects.header.waitUntilLoadingHasFinished(); } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 69c27936210951..afe8499a1c2ea3 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -100,7 +100,9 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { .subscribe({ next({ message, level }) { const msg = message.replace(/\\n/g, '\n'); - log[level === 'SEVERE' ? 'error' : 'debug'](`browser[${level}] ${msg}`); + log[level === 'SEVERE' || level === 'error' ? 'error' : 'debug']( + `browser[${level}] ${msg}` + ); }, }); diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index 87026ce25d9aa3..e9a4f3bcc4b1a9 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -37,6 +37,7 @@ export default async function({ readConfigFile }) { require.resolve('./test_suites/panel_actions'), require.resolve('./test_suites/embeddable_explorer'), require.resolve('./test_suites/core_plugins'), + require.resolve('./test_suites/management'), ], services: { ...functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/management_test_plugin/kibana.json b/test/plugin_functional/plugins/management_test_plugin/kibana.json new file mode 100644 index 00000000000000..e52b60b3a4e313 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "management_test_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["management_test_plugin"], + "server": false, + "ui": true, + "requiredPlugins": ["management"] +} diff --git a/test/plugin_functional/plugins/management_test_plugin/package.json b/test/plugin_functional/plugins/management_test_plugin/package.json new file mode 100644 index 00000000000000..656d92e9eb1f78 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "management_test_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/management_test_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/test/plugin_functional/plugins/management_test_plugin/public/index.ts b/test/plugin_functional/plugins/management_test_plugin/public/index.ts new file mode 100644 index 00000000000000..1efcc6cd3bbd62 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/public/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { + ManagementTestPlugin, + ManagementTestPluginSetup, + ManagementTestPluginStart, +} from './plugin'; + +export const plugin: PluginInitializer = () => + new ManagementTestPlugin(); diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx new file mode 100644 index 00000000000000..8b7cdd653ed8c3 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom'; +import { CoreSetup, Plugin } from 'kibana/public'; +import { ManagementSetup } from '../../../../../src/plugins/management/public'; + +export class ManagementTestPlugin + implements Plugin { + public setup(core: CoreSetup, { management }: { management: ManagementSetup }) { + const testSection = management.sections.register({ + id: 'test-section', + title: 'Test Section', + euiIconType: 'logoKibana', + order: 25, + }); + + testSection!.registerApp({ + id: 'test-management', + title: 'Management Test', + mount(params) { + params.setBreadcrumbs([{ text: 'Management Test' }]); + ReactDOM.render( + +

Hello from management test plugin

+ + + + Link to /one + + + + + Link to basePath + + + +
, + params.element + ); + + return () => { + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }); + return {}; + } + + public start() {} + public stop() {} +} + +export type ManagementTestPluginSetup = ReturnType; +export type ManagementTestPluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/management_test_plugin/tsconfig.json b/test/plugin_functional/plugins/management_test_plugin/tsconfig.json new file mode 100644 index 00000000000000..5fcaeafbb0d852 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/src/legacy/ui/public/utils/function.js b/test/plugin_functional/test_suites/management/index.js similarity index 76% rename from src/legacy/ui/public/utils/function.js rename to test/plugin_functional/test_suites/management/index.js index f9e7777d08bfe9..2bfc05547b2922 100644 --- a/src/legacy/ui/public/utils/function.js +++ b/test/plugin_functional/test_suites/management/index.js @@ -17,16 +17,8 @@ * under the License. */ -import _ from 'lodash'; - -/** - * Call all of the function in an array - * - * @param {array[functions]} arr - * @return {undefined} - */ -export function callEach(arr) { - return _.map(arr, function(fn) { - return _.isFunction(fn) ? fn() : undefined; +export default function({ loadTestFile }) { + describe('management plugin', () => { + loadTestFile(require.resolve('./management_plugin')); }); } diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js new file mode 100644 index 00000000000000..d65fb1dcd3a7e3 --- /dev/null +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -0,0 +1,40 @@ +/* + * 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 default function({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common']); + + describe('management plugin', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToActualUrl('kibana', 'management'); + }); + + it('should be able to navigate to management test app', async () => { + await testSubjects.click('test-management'); + await testSubjects.existOrFail('test-management-header'); + }); + + it('should be able to navigate within management test app', async () => { + await testSubjects.click('test-management-link-one'); + await testSubjects.click('test-management-link-basepath'); + await testSubjects.existOrFail('test-management-link-one'); + }); + }); +} diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index f79fe98e07bef4..2605655ed7e7ad 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -8,5 +8,8 @@ node scripts/es snapshot --license=oss --download-only; echo " -> Ensuring all functional tests are in a ciGroup" yarn run grunt functionalTests:ensureAllTestsInCiGroup; -echo " -> building and extracting OSS Kibana distributable for use in functional tests" -node scripts/build --debug --oss +# Do not build kibana for code coverage run +if [[ -z "$CODE_COVERAGE" ]] ; then + echo " -> building and extracting OSS Kibana distributable for use in functional tests" + node scripts/build --debug --oss +fi diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 1cb566c908dbfb..fccdb29ff512bd 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -2,22 +2,30 @@ source test/scripts/jenkins_test_setup.sh -if [[ -z "$IS_PIPELINE_JOB" ]] ; then - yarn run grunt functionalTests:ensureAllTestsInCiGroup; - node scripts/build --debug --oss; -else - installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" - destDir=${installDir}-${CI_WORKER_NUMBER} - cp -R "$installDir" "$destDir" +if [[ -z "$CODE_COVERAGE" ]] ; then + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + yarn run grunt functionalTests:ensureAllTestsInCiGroup; + node scripts/build --debug --oss; + else + installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" + destDir=${installDir}-${CI_WORKER_NUMBER} + cp -R "$installDir" "$destDir" - export KIBANA_INSTALL_DIR="$destDir" -fi + export KIBANA_INSTALL_DIR="$destDir" + fi + + checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; + + if [ "$CI_GROUP" == "1" ]; then + source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh + yarn run grunt run:pluginFunctionalTestsRelease --from=source; + yarn run grunt run:exampleFunctionalTestsRelease --from=source; + yarn run grunt run:interpreterFunctionalTestsRelease; + fi +else + echo " -> Running Functional tests with code coverage" -checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; + export NODE_OPTIONS=--max_old_space_size=8192 -if [ "$CI_GROUP" == "1" ]; then - source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh - yarn run grunt run:pluginFunctionalTestsRelease --from=source; - yarn run grunt run:exampleFunctionalTestsRelease --from=source; - yarn run grunt run:interpreterFunctionalTestsRelease; + yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; fi diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index 75610884b542fd..a8b5e8e4fdf976 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -4,4 +4,16 @@ set -e export TEST_BROWSER_HEADLESS=1 -"$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --dev; +if [[ -z "$CODE_COVERAGE" ]] ; then + "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --dev; +else + echo "NODE_ENV=$NODE_ENV" + echo " -> Running jest tests with coverage" + node scripts/jest --ci --verbose --coverage + echo "" + echo "" + echo " -> Running mocha tests with coverage" + yarn run grunt "test:mochaCoverage"; + echo "" + echo "" +fi diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 27f73c0b6e20d2..e0055085d9b372 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -4,33 +4,48 @@ set -e export TEST_BROWSER_HEADLESS=1 -echo " -> Running mocha tests" -cd "$XPACK_DIR" -checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:browser -echo "" -echo "" - -echo " -> Running jest tests" -cd "$XPACK_DIR" -checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose -echo "" -echo "" - -echo " -> Running SIEM cyclic dependency test" -cd "$XPACK_DIR" -checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node legacy/plugins/siem/scripts/check_circular_deps -echo "" -echo "" - -# FAILING: https://github.com/elastic/kibana/issues/44250 -# echo " -> Running jest contracts tests" -# cd "$XPACK_DIR" -# SLAPSHOT_ONLINE=true CONTRACT_ONLINE=true node scripts/jest_contract.js --ci --verbose -# echo "" -# echo "" - -# echo " -> Running jest integration tests" -# cd "$XPACK_DIR" -# node scripts/jest_integration --ci --verbose -# echo "" -# echo "" +if [[ -z "$CODE_COVERAGE" ]] ; then + echo " -> Running mocha tests" + cd "$XPACK_DIR" + checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:browser + echo "" + echo "" + + echo " -> Running jest tests" + cd "$XPACK_DIR" + checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose + echo "" + echo "" + + echo " -> Running SIEM cyclic dependency test" + cd "$XPACK_DIR" + checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node legacy/plugins/siem/scripts/check_circular_deps + echo "" + echo "" + + # FAILING: https://github.com/elastic/kibana/issues/44250 + # echo " -> Running jest contracts tests" + # cd "$XPACK_DIR" + # SLAPSHOT_ONLINE=true CONTRACT_ONLINE=true node scripts/jest_contract.js --ci --verbose + # echo "" + # echo "" + + # echo " -> Running jest integration tests" + # cd "$XPACK_DIR" + # node scripts/jest_integration --ci --verbose + # echo "" + # echo "" +else + echo " -> Running jest tests with coverage" + cd "$XPACK_DIR" + # build runtime for canvas + echo "NODE_ENV=$NODE_ENV" + node ./legacy/plugins/canvas/scripts/shareable_runtime + node scripts/jest --ci --verbose --coverage + # rename file in order to be unique one + test -f ../target/kibana-coverage/jest/coverage-final.json \ + && mv ../target/kibana-coverage/jest/coverage-final.json \ + ../target/kibana-coverage/jest/xpack-coverage-final.json + echo "" + echo "" +fi \ No newline at end of file diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 9f2bafc863f41a..20b12b302cb397 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -20,10 +20,13 @@ node scripts/functional_tests --assert-none-excluded \ --include-tag ciGroup9 \ --include-tag ciGroup10 -echo " -> building and extracting default Kibana distributable for use in functional tests" -cd "$KIBANA_DIR" -node scripts/build --debug --no-oss -linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" -installDir="$PARENT_DIR/install/kibana" -mkdir -p "$installDir" -tar -xzf "$linuxBuild" -C "$installDir" --strip=1 +# Do not build kibana for code coverage run +if [[ -z "$CODE_COVERAGE" ]] ; then + echo " -> building and extracting default Kibana distributable for use in functional tests" + cd "$KIBANA_DIR" + node scripts/build --debug --no-oss + linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" + installDir="$PARENT_DIR/install/kibana" + mkdir -p "$installDir" + tar -xzf "$linuxBuild" -C "$installDir" --strip=1 +fi diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index fba05f8f252d76..58c407a848ae3d 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -2,59 +2,60 @@ source test/scripts/jenkins_test_setup.sh -if [[ -z "$IS_PIPELINE_JOB" ]] ; then - echo " -> Ensuring all functional tests are in a ciGroup" +if [[ -z "$CODE_COVERAGE" ]] ; then + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + echo " -> Ensuring all functional tests are in a ciGroup" + cd "$XPACK_DIR" + node scripts/functional_tests --assert-none-excluded \ + --include-tag ciGroup1 \ + --include-tag ciGroup2 \ + --include-tag ciGroup3 \ + --include-tag ciGroup4 \ + --include-tag ciGroup5 \ + --include-tag ciGroup6 \ + --include-tag ciGroup7 \ + --include-tag ciGroup8 \ + --include-tag ciGroup9 \ + --include-tag ciGroup10 + fi + + cd "$KIBANA_DIR" + + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + echo " -> building and extracting default Kibana distributable for use in functional tests" + node scripts/build --debug --no-oss + + linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" + installDir="$PARENT_DIR/install/kibana" + + mkdir -p "$installDir" + tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + + export KIBANA_INSTALL_DIR="$installDir" + else + installDir="$PARENT_DIR/install/kibana" + destDir="${installDir}-${CI_WORKER_NUMBER}" + cp -R "$installDir" "$destDir" + + export KIBANA_INSTALL_DIR="$destDir" + fi + + echo " -> Running functional and api tests" cd "$XPACK_DIR" - node scripts/functional_tests --assert-none-excluded \ - --include-tag ciGroup1 \ - --include-tag ciGroup2 \ - --include-tag ciGroup3 \ - --include-tag ciGroup4 \ - --include-tag ciGroup5 \ - --include-tag ciGroup6 \ - --include-tag ciGroup7 \ - --include-tag ciGroup8 \ - --include-tag ciGroup9 \ - --include-tag ciGroup10 -fi - -cd "$KIBANA_DIR" - -if [[ -z "$IS_PIPELINE_JOB" ]] ; then - echo " -> building and extracting default Kibana distributable for use in functional tests" - node scripts/build --debug --no-oss - - linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" - installDir="$PARENT_DIR/install/kibana" - mkdir -p "$installDir" - tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + checks-reporter-with-killswitch "X-Pack Chrome Functional tests / Group ${CI_GROUP}" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --include-tag "ciGroup$CI_GROUP" - export KIBANA_INSTALL_DIR="$installDir" + echo "" + echo "" else - installDir="$PARENT_DIR/install/kibana" - destDir="${installDir}-${CI_WORKER_NUMBER}" - cp -R "$installDir" "$destDir" + echo " -> Running X-Pack functional tests with code coverage" + cd "$XPACK_DIR" - export KIBANA_INSTALL_DIR="$destDir" -fi + export NODE_OPTIONS=--max_old_space_size=8192 -echo " -> Running functional and api tests" -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "X-Pack Chrome Functional tests / Group ${CI_GROUP}" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "ciGroup$CI_GROUP" - -echo "" -echo "" - -# checks-reporter-with-killswitch "X-Pack Firefox Functional tests / Group ${CI_GROUP}" \ -# node scripts/functional_tests --debug --bail \ -# --kibana-install-dir "$installDir" \ -# --include-tag "ciGroup$CI_GROUP" \ -# --config "test/functional/config.firefox.js" -# echo "" -# echo "" + node scripts/functional_tests --debug --include-tag "ciGroup$CI_GROUP" +fi diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index c778dd799f6e59..5c6be70514c610 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -176,6 +176,18 @@ def uploadGcsArtifact(uploadPrefix, pattern) { ) } +def downloadCoverageArtifacts() { + def storageLocation = "gs://kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/" + def targetLocation = "/tmp/downloaded_coverage" + + sh "mkdir -p '${targetLocation}' && gsutil -m cp -r '${storageLocation}' '${targetLocation}'" +} + +def uploadCoverageArtifacts(prefix, pattern) { + def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/${prefix}" + uploadGcsArtifact(uploadPrefix, pattern) +} + def withGcsArtifactUpload(workerName, closure) { def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" def ARTIFACT_PATTERNS = [ @@ -201,6 +213,11 @@ def withGcsArtifactUpload(workerName, closure) { } } }) + + if (env.CODE_COVERAGE) { + sh 'tar -czf kibana-coverage.tar.gz target/kibana-coverage/**/*' + uploadGcsArtifact("kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/${workerName}", 'kibana-coverage.tar.gz') + } } def publishJunit() { diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index cd4414b5fdebe6..02904cc48e0306 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -30,7 +30,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { '^test_utils/find_test_subject': `${xPackKibanaDirectory}/test_utils/find_test_subject.ts`, }, coverageDirectory: '/../target/kibana-coverage/jest', - coverageReporters: ['html'], + coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html'], setupFiles: [ `${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`, `/dev-tools/jest/setup/polyfills.js`, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts index 35d81ba74fa72d..1da8b06e1587a9 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -142,7 +142,7 @@ describe('params validation', () => { ); expect(() => { - validateParams(actionType, { refresh: 'true' }); + validateParams(actionType, { refresh: 'foo' }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [refresh]: expected value of type [boolean] but got [string]"` ); diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index e8bc5d60a697b9..48f99ba5135b78 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -69,7 +69,7 @@ export class Plugin { plugins: ActionsPluginsSetup ): Promise { const config = await this.config$.pipe(first()).toPromise(); - this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); + this.adminClient = core.elasticsearch.adminClient; this.defaultKibanaIndex = (await this.kibana$.pipe(first()).toPromise()).index; this.licenseState = new LicenseState(plugins.licensing.license$); diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index ede95f76bf8113..fb16f579d4c701 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -5,7 +5,7 @@ */ import Hapi from 'hapi'; -import { first } from 'rxjs/operators'; + import { Services } from './types'; import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry } from './alert_type_registry'; @@ -62,7 +62,7 @@ export class Plugin { core: AlertingCoreSetup, plugins: AlertingPluginsSetup ): Promise { - this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); + this.adminClient = core.elasticsearch.adminClient; this.licenseState = new LicenseState(plugins.licensing.license$); diff --git a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts index f1df8eaec3064f..df23dff1c7127a 100644 --- a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts +++ b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts @@ -55,7 +55,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, dropdown_filter: { displayName: i18n.translate('xpack.canvas.elements.dropdownFilterDisplayName', { - defaultMessage: 'Dropdown Filter', + defaultMessage: 'Dropdown filter', }), help: i18n.translate('xpack.canvas.elements.dropdownFilterHelpText', { defaultMessage: 'A dropdown from which you can select values for an "exactly" filter', @@ -63,7 +63,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, horizontalBarChart: { displayName: i18n.translate('xpack.canvas.elements.horizontalBarChartDisplayName', { - defaultMessage: 'Horizontal Bar chart', + defaultMessage: 'Horizontal bar chart', }), help: i18n.translate('xpack.canvas.elements.horizontalBarChartHelpText', { defaultMessage: 'A customizable horizontal bar chart', @@ -71,7 +71,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, horizontalProgressBar: { displayName: i18n.translate('xpack.canvas.elements.horizontalProgressBarDisplayName', { - defaultMessage: 'Horizontal Progress Bar', + defaultMessage: 'Horizontal progress bar', }), help: i18n.translate('xpack.canvas.elements.horizontalProgressBarHelpText', { defaultMessage: 'Displays progress as a portion of a horizontal bar', @@ -79,7 +79,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, horizontalProgressPill: { displayName: i18n.translate('xpack.canvas.elements.horizontalProgressPillDisplayName', { - defaultMessage: 'Horizontal Progress Pill', + defaultMessage: 'Horizontal progress pill', }), help: i18n.translate('xpack.canvas.elements.horizontalProgressPillHelpText', { defaultMessage: 'Displays progress as a portion of a horizontal pill', @@ -103,10 +103,10 @@ export const getElementStrings = (): ElementStringDict => ({ }, markdown: { displayName: i18n.translate('xpack.canvas.elements.markdownDisplayName', { - defaultMessage: 'Markdown', + defaultMessage: 'Text', }), help: i18n.translate('xpack.canvas.elements.markdownHelpText', { - defaultMessage: 'Markup from Markdown', + defaultMessage: 'Add text using Markdown', }), }, metric: { @@ -135,7 +135,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, progressGauge: { displayName: i18n.translate('xpack.canvas.elements.progressGaugeDisplayName', { - defaultMessage: 'Progress Gauge', + defaultMessage: 'Progress gauge', }), help: i18n.translate('xpack.canvas.elements.progressGaugeHelpText', { defaultMessage: 'Displays progress as a portion of a gauge', @@ -143,7 +143,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, progressSemicircle: { displayName: i18n.translate('xpack.canvas.elements.progressSemicircleDisplayName', { - defaultMessage: 'Progress Semicircle', + defaultMessage: 'Progress semicircle', }), help: i18n.translate('xpack.canvas.elements.progressSemicircleHelpText', { defaultMessage: 'Displays progress as a portion of a semicircle', @@ -151,7 +151,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, progressWheel: { displayName: i18n.translate('xpack.canvas.elements.progressWheelDisplayName', { - defaultMessage: 'Progress Wheel', + defaultMessage: 'Progress wheel', }), help: i18n.translate('xpack.canvas.elements.progressWheelHelpText', { defaultMessage: 'Displays progress as a portion of a wheel', @@ -215,7 +215,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, verticalProgressBar: { displayName: i18n.translate('xpack.canvas.elements.verticalProgressBarDisplayName', { - defaultMessage: 'Vertical Progress Bar', + defaultMessage: 'Vertical progress bar', }), help: i18n.translate('xpack.canvas.elements.verticalProgressBarHelpText', { defaultMessage: 'Displays progress as a portion of a vertical bar', @@ -223,7 +223,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, verticalProgressPill: { displayName: i18n.translate('xpack.canvas.elements.verticalProgressPillDisplayName', { - defaultMessage: 'Vertical Progress Pill', + defaultMessage: 'Vertical progress pill', }), help: i18n.translate('xpack.canvas.elements.verticalProgressPillHelpText', { defaultMessage: 'Displays progress as a portion of a vertical pill', diff --git a/x-pack/legacy/plugins/canvas/public/lib/app_state.ts b/x-pack/legacy/plugins/canvas/public/lib/app_state.ts index be0f76b170c70a..955125b7131400 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/app_state.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/app_state.ts @@ -92,7 +92,7 @@ export function setFullscreen(payload: boolean) { } } -export function setAutoplayInterval(payload: string) { +export function setAutoplayInterval(payload: string | null) { const appState = getAppState(); const appValue = appState[AppStateKeys.AUTOPLAY_INTERVAL]; diff --git a/x-pack/legacy/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts b/x-pack/legacy/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts new file mode 100644 index 00000000000000..11ebdcdc51d4d9 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts @@ -0,0 +1,145 @@ +/* + * 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('../../../lib/app_state'); +jest.mock('../../../lib/router_provider'); + +import { workpadAutoplay } from '../workpad_autoplay'; +import { setAutoplayInterval } from '../../../lib/app_state'; +import { createTimeInterval } from '../../../lib/time_interval'; +// @ts-ignore Untyped local +import { routerProvider } from '../../../lib/router_provider'; + +const next = jest.fn(); +const dispatch = jest.fn(); +const getState = jest.fn(); +const routerMock = { navigateTo: jest.fn() }; +routerProvider.mockReturnValue(routerMock); + +const middleware = workpadAutoplay({ dispatch, getState })(next); + +const workpadState = { + persistent: { + workpad: { + id: 'workpad-id', + pages: ['page1', 'page2', 'page3'], + page: 0, + }, + }, +}; + +const autoplayState = { + ...workpadState, + transient: { + autoplay: { + inFlight: false, + enabled: true, + interval: 5000, + }, + fullscreen: true, + }, +}; + +const autoplayDisabledState = { + ...workpadState, + transient: { + autoplay: { + inFlight: false, + enabled: false, + interval: 5000, + }, + }, +}; + +const action = {}; + +describe('workpad autoplay middleware', () => { + beforeEach(() => { + dispatch.mockClear(); + jest.resetAllMocks(); + }); + + describe('app state', () => { + it('sets the app state to the interval from state when enabled', () => { + getState.mockReturnValue(autoplayState); + middleware(action); + + expect(setAutoplayInterval).toBeCalledWith( + createTimeInterval(autoplayState.transient.autoplay.interval) + ); + }); + + it('sets the app state to null when not enabled', () => { + getState.mockReturnValue(autoplayDisabledState); + middleware(action); + + expect(setAutoplayInterval).toBeCalledWith(null); + }); + }); + + describe('autoplay navigation', () => { + it('navigates forward after interval', () => { + jest.useFakeTimers(); + getState.mockReturnValue(autoplayState); + middleware(action); + + jest.advanceTimersByTime(autoplayState.transient.autoplay.interval + 1); + + expect(routerMock.navigateTo).toBeCalledWith('loadWorkpad', { + id: workpadState.persistent.workpad.id, + page: workpadState.persistent.workpad.page + 2, // (index + 1) + 1 more for 1 indexed page number + }); + + jest.useRealTimers(); + }); + + it('navigates from last page back to front', () => { + jest.useFakeTimers(); + const onLastPageState = { ...autoplayState }; + onLastPageState.persistent.workpad.page = onLastPageState.persistent.workpad.pages.length - 1; + + getState.mockReturnValue(autoplayState); + middleware(action); + + jest.advanceTimersByTime(autoplayState.transient.autoplay.interval + 1); + + expect(routerMock.navigateTo).toBeCalledWith('loadWorkpad', { + id: workpadState.persistent.workpad.id, + page: 1, + }); + + jest.useRealTimers(); + }); + + it('continues autoplaying', () => { + jest.useFakeTimers(); + getState.mockReturnValue(autoplayState); + middleware(action); + + jest.advanceTimersByTime(autoplayState.transient.autoplay.interval * 2 + 1); + expect(routerMock.navigateTo).toBeCalledTimes(2); + jest.useRealTimers(); + }); + + it('does not reset timer between middleware calls', () => { + jest.useFakeTimers(); + + getState.mockReturnValue(autoplayState); + middleware(action); + + // Advance until right before timeout + jest.advanceTimersByTime(autoplayState.transient.autoplay.interval - 1); + + // Run middleware again + middleware(action); + + // Advance timer + jest.advanceTimersByTime(1); + + expect(routerMock.navigateTo).toBeCalled(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts b/x-pack/legacy/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts new file mode 100644 index 00000000000000..2123c9606f1f0e --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts @@ -0,0 +1,161 @@ +/* + * 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('../../../legacy'); +jest.mock('ui/new_platform'); // actions/elements has some dependencies on ui/new_platform. +jest.mock('../../../lib/app_state'); + +import { workpadRefresh } from '../workpad_refresh'; +import { inFlightComplete } from '../../actions/resolved_args'; +// @ts-ignore untyped local +import { setRefreshInterval } from '../../actions/workpad'; +import { setRefreshInterval as setAppStateRefreshInterval } from '../../../lib/app_state'; + +import { createTimeInterval } from '../../../lib/time_interval'; + +const next = jest.fn(); +const dispatch = jest.fn(); +const getState = jest.fn(); + +const middleware = workpadRefresh({ dispatch, getState })(next); + +const refreshState = { + transient: { + refresh: { + interval: 5000, + }, + }, +}; + +const noRefreshState = { + transient: { + refresh: { + interval: 0, + }, + }, +}; + +const inFlightState = { + transient: { + refresh: { + interval: 5000, + }, + inFlight: true, + }, +}; + +describe('workpad refresh middleware', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('onInflightComplete', () => { + it('refreshes if interval gt 0', () => { + jest.useFakeTimers(); + getState.mockReturnValue(refreshState); + + middleware(inFlightComplete()); + + jest.runAllTimers(); + + expect(dispatch).toHaveBeenCalled(); + }); + + it('does not reset interval if another action occurs', () => { + jest.useFakeTimers(); + getState.mockReturnValue(refreshState); + + middleware(inFlightComplete()); + + jest.advanceTimersByTime(refreshState.transient.refresh.interval - 1); + + expect(dispatch).not.toHaveBeenCalled(); + middleware(inFlightComplete()); + + jest.advanceTimersByTime(1); + + expect(dispatch).toHaveBeenCalled(); + }); + + it('does not refresh if interval is 0', () => { + jest.useFakeTimers(); + getState.mockReturnValue(noRefreshState); + + middleware(inFlightComplete()); + + jest.runAllTimers(); + expect(dispatch).not.toHaveBeenCalled(); + }); + }); + + describe('setRefreshInterval', () => { + it('does nothing if refresh interval is unchanged', () => { + getState.mockReturnValue(refreshState); + + jest.useFakeTimers(); + const interval = 1; + middleware(setRefreshInterval(interval)); + jest.runAllTimers(); + + expect(setAppStateRefreshInterval).not.toBeCalled(); + }); + + it('sets the app refresh interval', () => { + getState.mockReturnValue(noRefreshState); + next.mockImplementation(() => { + getState.mockReturnValue(refreshState); + }); + + jest.useFakeTimers(); + const interval = 1; + middleware(setRefreshInterval(interval)); + + expect(setAppStateRefreshInterval).toBeCalledWith(createTimeInterval(interval)); + jest.runAllTimers(); + }); + + it('starts a refresh for the new interval', () => { + getState.mockReturnValue(refreshState); + jest.useFakeTimers(); + + const interval = 1000; + + middleware(inFlightComplete()); + + jest.runTimersToTime(refreshState.transient.refresh.interval - 1); + expect(dispatch).not.toBeCalled(); + + getState.mockReturnValue(noRefreshState); + next.mockImplementation(() => { + getState.mockReturnValue(refreshState); + }); + middleware(setRefreshInterval(interval)); + jest.runTimersToTime(1); + + expect(dispatch).not.toBeCalled(); + + jest.runTimersToTime(interval); + expect(dispatch).toBeCalled(); + }); + }); + + describe('inFlight in progress', () => { + it('requeues the refresh when inflight is active', () => { + jest.useFakeTimers(); + getState.mockReturnValue(inFlightState); + + middleware(inFlightComplete()); + jest.runTimersToTime(refreshState.transient.refresh.interval); + + expect(dispatch).not.toBeCalled(); + + getState.mockReturnValue(refreshState); + jest.runAllTimers(); + + expect(dispatch).toBeCalled(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_autoplay.js b/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_autoplay.ts similarity index 77% rename from x-pack/legacy/plugins/canvas/public/state/middleware/workpad_autoplay.js rename to x-pack/legacy/plugins/canvas/public/state/middleware/workpad_autoplay.ts index 886620c5404cd3..700905213f54cf 100644 --- a/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_autoplay.js +++ b/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_autoplay.ts @@ -4,16 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { inFlightComplete } from '../actions/resolved_args'; +import { Middleware } from 'redux'; +import { State } from '../../../types'; import { getFullscreen } from '../selectors/app'; import { getInFlight } from '../selectors/resolved_args'; import { getWorkpad, getPages, getSelectedPageIndex, getAutoplay } from '../selectors/workpad'; +// @ts-ignore untyped local import { routerProvider } from '../../lib/router_provider'; import { setAutoplayInterval } from '../../lib/app_state'; import { createTimeInterval } from '../../lib/time_interval'; -export const workpadAutoplay = ({ getState }) => next => { - let playTimeout; +export const workpadAutoplay: Middleware<{}, State> = ({ getState }) => next => { + let playTimeout: number | undefined; let displayInterval = 0; const router = routerProvider(); @@ -42,18 +44,22 @@ export const workpadAutoplay = ({ getState }) => next => { } } + stopAutoUpdate(); startDelayedUpdate(); } function stopAutoUpdate() { clearTimeout(playTimeout); // cancel any pending update requests + playTimeout = undefined; } function startDelayedUpdate() { - stopAutoUpdate(); - playTimeout = setTimeout(() => { - updateWorkpad(); - }, displayInterval); + if (!playTimeout) { + stopAutoUpdate(); + playTimeout = window.setTimeout(() => { + updateWorkpad(); + }, displayInterval); + } } return action => { @@ -68,21 +74,14 @@ export const workpadAutoplay = ({ getState }) => next => { if (autoplay.enabled) { setAutoplayInterval(createTimeInterval(autoplay.interval)); } else { - setAutoplayInterval(0); + setAutoplayInterval(null); } - // when in-flight requests are finished, update the workpad after a given delay - if (action.type === inFlightComplete.toString() && shouldPlay) { - startDelayedUpdate(); - } // create new update request - - // This middleware creates or destroys an interval that will cause workpad elements to update - // clear any pending timeout - stopAutoUpdate(); - // if interval is larger than 0, start the delayed update if (shouldPlay) { startDelayedUpdate(); + } else { + stopAutoUpdate(); } }; }; diff --git a/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_refresh.js b/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_refresh.ts similarity index 65% rename from x-pack/legacy/plugins/canvas/public/state/middleware/workpad_refresh.js rename to x-pack/legacy/plugins/canvas/public/state/middleware/workpad_refresh.ts index 32822529f320c9..f638c42ec2de08 100644 --- a/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_refresh.js +++ b/x-pack/legacy/plugins/canvas/public/state/middleware/workpad_refresh.ts @@ -4,18 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Middleware } from 'redux'; +import { State } from '../../../types'; +// @ts-ignore Untyped Local import { fetchAllRenderables } from '../actions/elements'; +// @ts-ignore Untyped Local import { setRefreshInterval } from '../actions/workpad'; import { inFlightComplete } from '../actions/resolved_args'; import { getInFlight } from '../selectors/resolved_args'; +import { getRefreshInterval } from '../selectors/workpad'; import { setRefreshInterval as setAppStateRefreshInterval } from '../../lib/app_state'; import { createTimeInterval } from '../../lib/time_interval'; -export const workpadRefresh = ({ dispatch, getState }) => next => { - let refreshTimeout; +export const workpadRefresh: Middleware<{}, State> = ({ dispatch, getState }) => next => { + let refreshTimeout: number | undefined; let refreshInterval = 0; function updateWorkpad() { + cancelDelayedUpdate(); + if (refreshInterval === 0) { return; } @@ -31,30 +38,43 @@ export const workpadRefresh = ({ dispatch, getState }) => next => { } } + function cancelDelayedUpdate() { + clearTimeout(refreshTimeout); + refreshTimeout = undefined; + } + function startDelayedUpdate() { - clearTimeout(refreshTimeout); // cancel any pending update requests - refreshTimeout = setTimeout(() => { - updateWorkpad(); - }, refreshInterval); + if (!refreshTimeout) { + clearTimeout(refreshTimeout); // cancel any pending update requests + refreshTimeout = window.setTimeout(() => { + updateWorkpad(); + }, refreshInterval); + } } return action => { + const previousRefreshInterval = getRefreshInterval(getState()); next(action); + refreshInterval = getRefreshInterval(getState()); + // when in-flight requests are finished, update the workpad after a given delay if (action.type === inFlightComplete.toString() && refreshInterval > 0) { startDelayedUpdate(); } // create new update request // This middleware creates or destroys an interval that will cause workpad elements to update - if (action.type === setRefreshInterval.toString()) { + if ( + action.type === setRefreshInterval.toString() && + previousRefreshInterval !== refreshInterval + ) { // update the refresh interval refreshInterval = action.payload; setAppStateRefreshInterval(createTimeInterval(refreshInterval)); // clear any pending timeout - clearTimeout(refreshTimeout); + cancelDelayedUpdate(); // if interval is larger than 0, start the delayed update if (refreshInterval > 0) { diff --git a/x-pack/legacy/plugins/canvas/server/sample_data/ecommerce_saved_objects.json b/x-pack/legacy/plugins/canvas/server/sample_data/ecommerce_saved_objects.json index 39970c6d66b7af..113fba55553760 100644 --- a/x-pack/legacy/plugins/canvas/server/sample_data/ecommerce_saved_objects.json +++ b/x-pack/legacy/plugins/canvas/server/sample_data/ecommerce_saved_objects.json @@ -194,7 +194,7 @@ "height": 56, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" fields=\"customer_gender\" \n| ply by=\"customer_gender\" expression={rowCount | as \"percentage\"} \n| filterrows {getCell \"customer_gender\" | any {eq \"FEMALE\"}} \n| markdown {getCell \"percentage\"} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#eb6c66\" weight=\"normal\" underline=false italic=false}" + "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"femaleCount\" exp={getCell \"customer_gender\" | if {compare to=\"FEMALE\"} then=1 else=0} | math \"round(100 * sum(femaleCount) / count(femaleCount))\" | markdown {context} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#eb6c66\" weight=\"normal\" underline=false italic=false}" }, { "id": "element-9e0b6230-2bc9-4995-8207-043e3063faeb", @@ -205,7 +205,7 @@ "height": 56, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" fields=\"customer_gender\" \n| ply by=\"customer_gender\" expression={rowCount | as \"percentage\"} \n| filterrows {getCell \"customer_gender\" | any {eq \"MALE\"}} \n| markdown {getCell \"percentage\"} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#f8bd4a\" weight=\"normal\" underline=false italic=false}" + "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"MALE\"} then=1 else=0} | math \"round(100 * sum(maleCount) / count(maleCount))\" | markdown {context} \"%\" font={font family=\"Avenir\" size=48 align=\"center\" color=\"#f8bd4a\" weight=\"normal\" underline=false italic=false}" }, { "id": "element-2185edff-ac50-4162-b583-3bfd6469e925", @@ -959,23 +959,23 @@ "id": "element-b8f13a87-b781-42d5-a663-5dbd4f645d6d", "position": { "left": 837, - "top": 472, + "top": 468, "width": 91, "height": 63, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" fields=\"customer_gender\" \n| ply by=\"customer_gender\" expression={rowCount | as \"percentage\"} \n| filterrows {getCell \"customer_gender\" | any {eq \"FEMALE\"}} \n| markdown {getCell \"percentage\"} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#F05A24\" weight=\"bold\" underline=false italic=false}" + "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"femaleCount\" exp={getCell \"customer_gender\" | if {compare to=\"FEMALE\"} then=1 else=0} | math \"round(100 * sum(femaleCount) / count(femaleCount))\" | markdown {context} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#F05A24\" weight=\"bold\" underline=false italic=false}" }, { "id": "element-86b06b67-893e-4555-ad38-7fba9ea3153b", "position": { "left": 837, - "top": 219, + "top": 215, "width": 90, "height": 72, "angle": 0 }, - "expression": "esdocs index=\"kibana_sample_data_ecommerce\" fields=\"customer_gender\" \n| ply by=\"customer_gender\" expression={rowCount | as \"percentage\"} \n| filterrows {getCell \"customer_gender\" | any {eq \"MALE\"}} \n| markdown {getCell \"percentage\"} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#00A89C\" weight=\"bold\" underline=false italic=false}" + "expression": "esdocs index=\"kibana_sample_data_ecommerce\" sort=\"order_date, desc\" fields=\"customer_gender\" | mapColumn \"maleCount\" exp={getCell \"customer_gender\" | if {compare to=\"MALE\"} then=1 else=0} | math \"round(100 * sum(maleCount) / count(maleCount))\" | markdown {context} font={font family=\"nexa bold, Avenir\" size=48 align=\"center\" color=\"#00A89C\" weight=\"bold\" underline=false italic=false}" }, { "id": "element-507337d9-6e0e-4752-8770-6ebe88e9b3da", diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx index bf4a09769d254b..6516993397e025 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; import { transparentize } from 'polished'; import euiStyled from '../../../../../../common/eui_styled_components'; @@ -21,28 +21,26 @@ import { LogEntryColumnWidths, } from './log_entry_column'; import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel'; -import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { LogPositionState } from '../../../containers/logs/log_position'; import { localizedDate } from '../../../utils/formatters/datetime'; export const LogColumnHeaders: React.FunctionComponent<{ columnConfigurations: LogColumnConfiguration[]; columnWidths: LogEntryColumnWidths; }> = ({ columnConfigurations, columnWidths }) => { + const { firstVisiblePosition } = useContext(LogPositionState.Context); return ( {columnConfigurations.map(columnConfiguration => { if (isTimestampLogColumnConfiguration(columnConfiguration)) { return ( - - {({ firstVisiblePosition }) => ( - - {firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'} - - )} - + + {firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'} + ); } else if (isMessageLogColumnConfiguration(columnConfiguration)) { return ( diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index a5b85788fdea9c..84bcfa911125a3 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -55,17 +55,13 @@ interface ScrollableLogTextStreamViewProps { setFlyoutVisibility: (visible: boolean) => void; highlightedItem: string | null; currentHighlightKey: UniqueTimeKey | null; - scrollLock: { - enable: () => void; - disable: () => void; - isEnabled: boolean; - }; } interface ScrollableLogTextStreamViewState { target: TimeKey | null; targetId: string | null; items: StreamItem[]; + isScrollLocked: boolean; } export class ScrollableLogTextStreamView extends React.PureComponent< @@ -81,8 +77,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< // Prevent new entries from being appended and moving the stream forward when // the user has scrolled up during live streaming - const nextItems = - hasItems && nextProps.scrollLock.isEnabled ? prevState.items : nextProps.items; + const nextItems = hasItems && prevState.isScrollLocked ? prevState.items : nextProps.items; if (nextProps.isStreaming && hasItems) { return { @@ -121,6 +116,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< target: null, targetId: null, items: props.items, + isScrollLocked: false, }; } @@ -137,9 +133,8 @@ export class ScrollableLogTextStreamView extends React.PureComponent< lastLoadedTime, scale, wrap, - scrollLock, } = this.props; - const { targetId, items } = this.state; + const { targetId, items, isScrollLocked } = this.state; const hasItems = items.length > 0; return ( @@ -187,7 +182,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< target={targetId} hideScrollbar={true} data-test-subj={'logStream'} - isLocked={scrollLock.isEnabled} + isLocked={isScrollLocked} entriesCount={items.length} > {registerChild => ( @@ -248,7 +243,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< lastStreamingUpdate={isStreaming ? lastLoadedTime : null} onLoadMore={this.handleLoadNewerItems} /> - {scrollLock.isEnabled && ( + {isScrollLocked && ( { if (fromScroll && this.props.isStreaming) { - this.props.scrollLock[pagesBelow === 0 ? 'disable' : 'enable'](); + this.setState({ + isScrollLocked: pagesBelow !== 0, + }); } this.props.reportVisibleInterval({ endKey: parseStreamItemId(bottomChild), @@ -322,11 +319,11 @@ export class ScrollableLogTextStreamView extends React.PureComponent< ); private handleJumpToTail = () => { - const { items, scrollLock } = this.props; - scrollLock.disable(); + const { items } = this.props; const lastItemTarget = getStreamItemId(items[items.length - 1]); this.setState({ targetId: lastItemTarget, + isScrollLocked: false, }); }; } diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 5f3d1a63e72eb3..e65753ef24e9e3 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -26,6 +26,7 @@ interface FieldsConfigurationPanelProps { podFieldProps: InputFieldProps; tiebreakerFieldProps: InputFieldProps; timestampFieldProps: InputFieldProps; + displaySettings: 'metrics' | 'logs'; } export const FieldsConfigurationPanel = ({ @@ -36,6 +37,7 @@ export const FieldsConfigurationPanel = ({ podFieldProps, tiebreakerFieldProps, timestampFieldProps, + displaySettings, }: FieldsConfigurationPanelProps) => ( @@ -94,193 +96,201 @@ export const FieldsConfigurationPanel = ({ /> - - - - } - description={ - - } - > - _doc, - }} - /> - } - isInvalid={tiebreakerFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - container.id, - }} - /> - } - isInvalid={containerFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - host.name, - }} - /> - } - isInvalid={hostFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - kubernetes.pod.uid, - }} - /> - } - isInvalid={podFieldProps.isInvalid} - label={ - - } - > - - - + {displaySettings === 'logs' && ( + <> + + + + } + description={ + + } + > + _doc, + }} + /> + } + isInvalid={tiebreakerFieldProps.isInvalid} + label={ + + } + > + + + + + )} + {displaySettings === 'metrics' && ( + <> + + + + } + description={ + + } + > + container.id, + }} + /> + } + isInvalid={containerFieldProps.isInvalid} + label={ + + } + > + + + + + + + } + description={ + + } + > + host.name, + }} + /> + } + isInvalid={hostFieldProps.isInvalid} + label={ + + } + > + + + + + + + } + description={ + + } + > + kubernetes.pod.uid, + }} + /> + } + isInvalid={podFieldProps.isInvalid} + label={ + + } + > + + + + + )} ); diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx index e779b35975ec3f..eed6768c8846cb 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx @@ -23,6 +23,7 @@ interface IndicesConfigurationPanelProps { readOnly: boolean; logAliasFieldProps: InputFieldProps; metricAliasFieldProps: InputFieldProps; + displaySettings: 'metrics' | 'logs'; } export const IndicesConfigurationPanel = ({ @@ -30,6 +31,7 @@ export const IndicesConfigurationPanel = ({ readOnly, logAliasFieldProps, metricAliasFieldProps, + displaySettings, }: IndicesConfigurationPanelProps) => ( @@ -41,101 +43,105 @@ export const IndicesConfigurationPanel = ({ - - - - } - description={ - - } - > - metricbeat-*, - }} - /> + {displaySettings === 'metrics' && ( + + + } - isInvalid={metricAliasFieldProps.isInvalid} - label={ + description={ } > - - - - - - - } - description={ - - } - > - filebeat-*, - }} + helpText={ + metricbeat-*, + }} + /> + } + isInvalid={metricAliasFieldProps.isInvalid} + label={ + + } + > + + + + )} + {displaySettings === 'logs' && ( + + + } - isInvalid={logAliasFieldProps.isInvalid} - label={ + description={ } > - - - + helpText={ + filebeat-*, + }} + /> + } + isInvalid={logAliasFieldProps.isInvalid} + label={ + + } + > + + + + )} ); diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx index 31afedc8f31eea..68dbdf38e6af6c 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx @@ -25,13 +25,16 @@ import { IndicesConfigurationPanel } from './indices_configuration_panel'; import { NameConfigurationPanel } from './name_configuration_panel'; import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel'; import { useSourceConfigurationFormState } from './source_configuration_form_state'; +import { SourceLoadingPage } from '../source_loading_page'; interface SourceConfigurationSettingsProps { shouldAllowEdit: boolean; + displaySettings: 'metrics' | 'logs'; } export const SourceConfigurationSettings = ({ shouldAllowEdit, + displaySettings, }: SourceConfigurationSettingsProps) => { const { createSourceConfiguration, @@ -80,7 +83,10 @@ export const SourceConfigurationSettings = ({ source, ]); - if (!source || !source.configuration) { + if (!source) { + return ; + } + if (!source.configuration) { return null; } @@ -112,6 +118,7 @@ export const SourceConfigurationSettings = ({ logAliasFieldProps={indicesConfigurationProps.logAlias} metricAliasFieldProps={indicesConfigurationProps.metricAlias} readOnly={!isWriteable} + displaySettings={displaySettings} /> @@ -124,18 +131,21 @@ export const SourceConfigurationSettings = ({ readOnly={!isWriteable} tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField} timestampFieldProps={indicesConfigurationProps.timestampField} + displaySettings={displaySettings} /> - - - + {displaySettings === 'logs' && ( + + + + )} {errors.length > 0 ? ( <> diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index bdd08ab6b366f7..785531db2ff5ed 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -28,6 +28,8 @@ interface WaffleInventorySwitcherProps { changeNodeType: (nodeType: InfraNodeType) => void; changeGroupBy: (groupBy: InfraSnapshotGroupbyInput[]) => void; changeMetric: (metric: InfraSnapshotMetricInput) => void; + changeAccount: (id: string) => void; + changeRegion: (name: string) => void; } const getDisplayNameForType = (type: InventoryItemType) => { @@ -39,6 +41,8 @@ export const WaffleInventorySwitcher: React.FC = ( changeNodeType, changeGroupBy, changeMetric, + changeAccount, + changeRegion, nodeType, }) => { const [isOpen, setIsOpen] = useState(false); @@ -49,12 +53,14 @@ export const WaffleInventorySwitcher: React.FC = ( closePopover(); changeNodeType(targetNodeType); changeGroupBy([]); + changeAccount(''); + changeRegion(''); const inventoryModel = findInventoryModel(targetNodeType); changeMetric({ type: inventoryModel.metrics.defaultSnapshot as InfraSnapshotMetricType, }); }, - [closePopover, changeNodeType, changeGroupBy, changeMetric] + [closePopover, changeNodeType, changeGroupBy, changeMetric, changeAccount, changeRegion] ); const goToHost = useCallback(() => goToNodeType('host' as InfraNodeType), [goToNodeType]); const goToK8 = useCallback(() => goToNodeType('pod' as InfraNodeType), [goToNodeType]); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts index 3020ad7eb5f84e..ed82c0854cdea3 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts @@ -5,7 +5,7 @@ */ import { useEffect, useState, useReducer, useCallback } from 'react'; import createContainer from 'constate'; -import { pick, throttle } from 'lodash'; +import { pick, throttle, omit } from 'lodash'; import { useGraphQLQueries } from './gql_queries'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; import { InfraLogEntry } from './types'; @@ -45,6 +45,7 @@ interface LogEntriesProps { pagesAfterEnd: number | null; sourceId: string; isAutoReloading: boolean; + jumpToTargetPosition: (position: TimeKey) => void; } type FetchEntriesParams = Omit; @@ -65,7 +66,7 @@ export type LogEntriesStateParams = { } & LogEntriesResponse; export interface LogEntriesCallbacks { - fetchNewerEntries: () => Promise; + fetchNewerEntries: () => Promise; } export const logEntriesInitialCallbacks = { fetchNewerEntries: async () => {}, @@ -127,10 +128,13 @@ const useFetchEntriesEffect = ( const [prevParams, cachePrevParams] = useState(props); const [startedStreaming, setStartedStreaming] = useState(false); - const runFetchNewEntriesRequest = async () => { + const runFetchNewEntriesRequest = async (override = {}) => { dispatch({ type: Action.FetchingNewEntries }); try { - const payload = await getLogEntriesAround(props); + const payload = await getLogEntriesAround({ + ...omit(props, 'jumpToTargetPosition'), + ...override, + }); dispatch({ type: Action.ReceiveNewEntries, payload }); } catch (e) { dispatch({ type: Action.ErrorOnNewEntries }); @@ -150,6 +154,7 @@ const useFetchEntriesEffect = ( type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, payload, }); + return payload.entriesEnd; } catch (e) { dispatch({ type: Action.ErrorOnMoreEntries }); } @@ -185,19 +190,37 @@ const useFetchEntriesEffect = ( const fetchNewerEntries = useCallback( throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), - [props] + [props, state.entriesEnd] ); - const streamEntriesEffectDependencies = [props.isAutoReloading, state.isLoadingMore]; + const streamEntriesEffectDependencies = [ + props.isAutoReloading, + state.isLoadingMore, + state.isReloading, + ]; const streamEntriesEffect = () => { (async () => { - if (props.isAutoReloading && !state.isLoadingMore) { + if (props.isAutoReloading && !state.isLoadingMore && !state.isReloading) { if (startedStreaming) { await new Promise(res => setTimeout(res, 5000)); } else { + const nowKey = { + tiebreaker: 0, + time: Date.now(), + }; + props.jumpToTargetPosition(nowKey); setStartedStreaming(true); + if (state.hasMoreAfterEnd) { + runFetchNewEntriesRequest({ + timeKey: nowKey, + }); + return; + } + } + const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After); + if (newEntriesEnd) { + props.jumpToTargetPosition(newEntriesEnd); } - fetchNewerEntries(); } else if (!props.isAutoReloading) { setStartedStreaming(false); } diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts index 605e90914b174a..0d548a6a78b1c4 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts @@ -5,4 +5,3 @@ */ export * from './log_highlights'; -export * from './redux_bridges'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index fa1ccb4efa4bb7..a4a94851ad383d 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -9,9 +9,9 @@ import { useState, useContext } from 'react'; import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; -import { useReduxBridgeSetters } from './redux_bridge_setters'; import { useLogSummaryBufferInterval } from '../log_summary'; import { LogViewConfiguration } from '../log_view_configuration'; +import { LogPositionState } from '../log_position'; import { TimeKey } from '../../../../common/time'; export const useLogHighlightsState = ({ @@ -28,14 +28,7 @@ export const useLogHighlightsState = ({ filterQuery: string | null; }) => { const [highlightTerms, setHighlightTerms] = useState([]); - const { - visibleMidpoint, - setFilterQuery, - setVisibleMidpoint, - jumpToTarget, - setJumpToTarget, - } = useReduxBridgeSetters(); - + const { visibleMidpoint, jumpToTargetPosition } = useContext(LogPositionState.Context); const { intervalSize: summaryIntervalSize } = useContext(LogViewConfiguration.Context); const { start: summaryStart, @@ -79,25 +72,22 @@ export const useLogHighlightsState = ({ visibleMidpoint, logEntryHighlights, highlightTerms, - jumpToTarget, + jumpToTargetPosition, }); return { highlightTerms, setHighlightTerms, - setFilterQuery, logEntryHighlights, logEntryHighlightsById, logSummaryHighlights, loadLogEntryHighlightsRequest, loadLogSummaryHighlightsRequest, - setVisibleMidpoint, currentHighlightKey, hasPreviousHighlight, hasNextHighlight, goToPreviousHighlight, goToNextHighlight, - setJumpToTarget, }; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx index 62a43a5412825b..7557550883f113 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx @@ -17,12 +17,12 @@ import { LogEntryHighlights } from './log_entry_highlights'; export const useNextAndPrevious = ({ highlightTerms, - jumpToTarget, + jumpToTargetPosition, logEntryHighlights, visibleMidpoint, }: { highlightTerms: string[]; - jumpToTarget: (target: TimeKey) => void; + jumpToTargetPosition: (target: TimeKey) => void; logEntryHighlights: LogEntryHighlights | undefined; visibleMidpoint: TimeKey | null; }) => { @@ -41,9 +41,9 @@ export const useNextAndPrevious = ({ useEffect(() => { if (currentTimeKey) { - jumpToTarget(currentTimeKey); + jumpToTargetPosition(currentTimeKey); } - }, [currentTimeKey, jumpToTarget]); + }, [currentTimeKey, jumpToTargetPosition]); useEffect(() => { if (currentTimeKey === null && entries.length > 0) { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx deleted file mode 100644 index 0e778f35188f0a..00000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx +++ /dev/null @@ -1,23 +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 { useState } from 'react'; -import { TimeKey } from '../../../../common/time'; - -export const useReduxBridgeSetters = () => { - const [filterQuery, setFilterQuery] = useState(null); - const [visibleMidpoint, setVisibleMidpoint] = useState(null); - const [jumpToTarget, setJumpToTarget] = useState<(target: TimeKey) => void>(() => undefined); - - return { - filterQuery, - visibleMidpoint, - setFilterQuery, - setVisibleMidpoint, - jumpToTarget, - setJumpToTarget, - }; -}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx deleted file mode 100644 index 393caae52511f8..00000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ /dev/null @@ -1,41 +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, { useEffect, useContext } from 'react'; - -import { TimeKey } from '../../../../common/time'; -import { withLogPosition } from '../with_log_position'; -import { LogHighlightsState } from './log_highlights'; - -// Bridges Redux container state with Hooks state. Once state is moved fully from -// Redux to Hooks this can be removed. - -export const LogHighlightsPositionBridge = withLogPosition( - ({ - visibleMidpoint, - jumpToTargetPosition, - }: { - visibleMidpoint: TimeKey | null; - jumpToTargetPosition: (target: TimeKey) => void; - }) => { - const { setJumpToTarget, setVisibleMidpoint } = useContext(LogHighlightsState.Context); - useEffect(() => { - setVisibleMidpoint(visibleMidpoint); - }, [setVisibleMidpoint, visibleMidpoint]); - - useEffect(() => { - setJumpToTarget(() => jumpToTargetPosition); - }, [jumpToTargetPosition, setJumpToTarget]); - - return null; - } -); - -export const LogHighlightsBridge = () => ( - <> - - -); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts index 7cc8050aafd146..54a2bc88ad3d01 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts @@ -4,32 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useContext } from 'react'; -import createContainer from 'constate'; -import { ReduxStateContext } from '../../../utils/redux_context'; -import { logPositionSelectors as logPositionReduxSelectors } from '../../../store/local/selectors'; -import { TimeKey } from '../../../../common/time'; - -export const useLogPositionState = () => { - const { local: state } = useContext(ReduxStateContext); - const timeKey = logPositionReduxSelectors.selectVisibleMidpointOrTarget(state); - const pages = logPositionReduxSelectors.selectPagesBeforeAndAfter(state); - const isAutoReloading = logPositionReduxSelectors.selectIsAutoReloading(state); - return { timeKey, isAutoReloading, ...pages }; -}; - -export interface LogPositionStateParams { - timeKey: TimeKey | null; - pagesAfterEnd: number | null; - pagesBeforeStart: number | null; - isAutoReloading: boolean; -} - -export const logPositionInitialState = { - timeKey: null, - pagesAfterEnd: null, - pagesBeforeStart: null, - isAutoReloading: false, -}; - -export const LogPositionState = createContainer(useLogPositionState); +export * from './log_position_state'; +export * from './with_log_position_url_state'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/log_position_state.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/log_position_state.ts new file mode 100644 index 00000000000000..1a8274024bd265 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/log_position_state.ts @@ -0,0 +1,107 @@ +/* + * 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, useMemo, useEffect, useCallback } from 'react'; +import createContainer from 'constate'; +import { TimeKey } from '../../../../common/time'; + +type TimeKeyOrNull = TimeKey | null; + +interface VisiblePositions { + startKey: TimeKeyOrNull; + middleKey: TimeKeyOrNull; + endKey: TimeKeyOrNull; + pagesAfterEnd: number; + pagesBeforeStart: number; +} + +export interface LogPositionStateParams { + targetPosition: TimeKeyOrNull; + isAutoReloading: boolean; + firstVisiblePosition: TimeKeyOrNull; + pagesBeforeStart: number; + pagesAfterEnd: number; + visibleMidpoint: TimeKeyOrNull; + visibleMidpointTime: number | null; + visibleTimeInterval: { start: number; end: number } | null; +} + +export interface LogPositionCallbacks { + jumpToTargetPosition: (pos: TimeKeyOrNull) => void; + jumpToTargetPositionTime: (time: number) => void; + reportVisiblePositions: (visPos: VisiblePositions) => void; + startLiveStreaming: () => void; + stopLiveStreaming: () => void; +} + +const useVisibleMidpoint = (middleKey: TimeKeyOrNull, targetPosition: TimeKeyOrNull) => { + // Of the two dependencies `middleKey` and `targetPosition`, return + // whichever one was the most recently updated. This allows the UI controls + // to display a newly-selected `targetPosition` before loading new data; + // otherwise the previous `middleKey` would linger in the UI for the entirety + // of the loading operation, which the user could perceive as unresponsiveness + const [store, update] = useState({ + middleKey, + targetPosition, + currentValue: middleKey || targetPosition, + }); + useEffect(() => { + if (middleKey !== store.middleKey) { + update({ targetPosition, middleKey, currentValue: middleKey }); + } else if (targetPosition !== store.targetPosition) { + update({ targetPosition, middleKey, currentValue: targetPosition }); + } + }, [middleKey, targetPosition]); // eslint-disable-line react-hooks/exhaustive-deps + + return store.currentValue; +}; + +export const useLogPositionState: () => LogPositionStateParams & LogPositionCallbacks = () => { + const [targetPosition, jumpToTargetPosition] = useState(null); + const [isAutoReloading, setIsAutoReloading] = useState(false); + const [visiblePositions, reportVisiblePositions] = useState({ + endKey: null, + middleKey: null, + startKey: null, + pagesBeforeStart: Infinity, + pagesAfterEnd: Infinity, + }); + + const { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd } = visiblePositions; + + const visibleMidpoint = useVisibleMidpoint(middleKey, targetPosition); + + const visibleTimeInterval = useMemo( + () => (startKey && endKey ? { start: startKey.time, end: endKey.time } : null), + [startKey, endKey] + ); + + const state = { + targetPosition, + isAutoReloading, + firstVisiblePosition: startKey, + pagesBeforeStart, + pagesAfterEnd, + visibleMidpoint, + visibleMidpointTime: visibleMidpoint ? visibleMidpoint.time : null, + visibleTimeInterval, + }; + + const callbacks = { + jumpToTargetPosition, + jumpToTargetPositionTime: useCallback( + (time: number) => jumpToTargetPosition({ tiebreaker: 0, time }), + [jumpToTargetPosition] + ), + reportVisiblePositions, + startLiveStreaming: useCallback(() => setIsAutoReloading(true), [setIsAutoReloading]), + stopLiveStreaming: useCallback(() => setIsAutoReloading(false), [setIsAutoReloading]), + }; + + return { ...state, ...callbacks }; +}; + +export const LogPositionState = createContainer(useLogPositionState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx new file mode 100644 index 00000000000000..a877750587be4c --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx @@ -0,0 +1,94 @@ +/* + * 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, useMemo } from 'react'; + +import { pickTimeKey } from '../../../../common/time'; +import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state'; +import { LogPositionState, LogPositionStateParams } from './log_position_state'; + +/** + * Url State + */ + +interface LogPositionUrlState { + position: LogPositionStateParams['visibleMidpoint'] | undefined; + streamLive?: boolean | undefined; +} + +export const WithLogPositionUrlState = () => { + const { + visibleMidpoint, + isAutoReloading, + jumpToTargetPosition, + jumpToTargetPositionTime, + startLiveStreaming, + stopLiveStreaming, + } = useContext(LogPositionState.Context); + const urlState = useMemo( + () => ({ + position: visibleMidpoint ? pickTimeKey(visibleMidpoint) : null, + streamLive: isAutoReloading, + }), + [visibleMidpoint, isAutoReloading] + ); + return ( + { + if (newUrlState && newUrlState.position) { + jumpToTargetPosition(newUrlState.position); + } + if (newUrlState && newUrlState.streamLive) { + startLiveStreaming(); + } else if ( + newUrlState && + typeof newUrlState.streamLive !== 'undefined' && + !newUrlState.streamLive + ) { + stopLiveStreaming(); + } + }} + onInitialize={(initialUrlState: LogPositionUrlState | undefined) => { + if (initialUrlState && initialUrlState.position) { + jumpToTargetPosition(initialUrlState.position); + } else { + jumpToTargetPositionTime(Date.now()); + } + if (initialUrlState && initialUrlState.streamLive) { + startLiveStreaming(); + } + }} + /> + ); +}; + +const mapToUrlState = (value: any): LogPositionUrlState | undefined => + value + ? { + position: mapToPositionUrlState(value.position), + streamLive: mapToStreamLiveUrlState(value.streamLive), + } + : undefined; + +const mapToPositionUrlState = (value: any) => + value && typeof value.time === 'number' && typeof value.tiebreaker === 'number' + ? pickTimeKey(value) + : undefined; + +const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); + +export const replaceLogPositionInQueryString = (time: number) => + Number.isNaN(time) + ? (value: string) => value + : replaceStateKeyInQueryString('logPosition', { + position: { + time, + tiebreaker: 0, + }, + }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts index 7971c12bdfda0d..4db0d2e645448c 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts @@ -5,40 +5,34 @@ */ import { useContext } from 'react'; -import { connect } from 'react-redux'; -import { logPositionSelectors, State } from '../../../store'; import { RendererFunction } from '../../../utils/typed_react'; import { Source } from '../../source'; import { LogViewConfiguration } from '../log_view_configuration'; import { LogSummaryBuckets, useLogSummary } from './log_summary'; import { LogFilterState } from '../log_filter'; +import { LogPositionState } from '../log_position'; -export const WithSummary = connect((state: State) => ({ - visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state), -}))( - ({ - children, - visibleMidpointTime, - }: { - children: RendererFunction<{ - buckets: LogSummaryBuckets; - start: number | null; - end: number | null; - }>; - visibleMidpointTime: number | null; - }) => { - const { intervalSize } = useContext(LogViewConfiguration.Context); - const { sourceId } = useContext(Source.Context); - const { filterQuery } = useContext(LogFilterState.Context); +export const WithSummary = ({ + children, +}: { + children: RendererFunction<{ + buckets: LogSummaryBuckets; + start: number | null; + end: number | null; + }>; +}) => { + const { intervalSize } = useContext(LogViewConfiguration.Context); + const { sourceId } = useContext(Source.Context); + const { filterQuery } = useContext(LogFilterState.Context); + const { visibleMidpointTime } = useContext(LogPositionState.Context); - const { buckets, start, end } = useLogSummary( - sourceId, - visibleMidpointTime, - intervalSize, - filterQuery - ); + const { buckets, start, end } = useLogSummary( + sourceId, + visibleMidpointTime, + intervalSize, + filterQuery + ); - return children({ buckets, start, end }); - } -); + return children({ buckets, start, end }); +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx deleted file mode 100644 index 075f3904d994c2..00000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx +++ /dev/null @@ -1,127 +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 from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; - -import { pickTimeKey } from '../../../common/time'; -import { logPositionActions, logPositionSelectors, State } from '../../store'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state'; - -export const withLogPosition = connect( - (state: State) => ({ - firstVisiblePosition: logPositionSelectors.selectFirstVisiblePosition(state), - isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), - isScrollLocked: logPositionSelectors.selectAutoReloadScrollLock(state), - lastVisiblePosition: logPositionSelectors.selectFirstVisiblePosition(state), - targetPosition: logPositionSelectors.selectTargetPosition(state), - urlState: selectPositionUrlState(state), - visibleTimeInterval: logPositionSelectors.selectVisibleTimeInterval(state), - visibleMidpoint: logPositionSelectors.selectVisibleMidpointOrTarget(state), - visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state), - }), - bindPlainActionCreators({ - jumpToTargetPosition: logPositionActions.jumpToTargetPosition, - jumpToTargetPositionTime: logPositionActions.jumpToTargetPositionTime, - reportVisiblePositions: logPositionActions.reportVisiblePositions, - startLiveStreaming: logPositionActions.startAutoReload, - stopLiveStreaming: logPositionActions.stopAutoReload, - scrollLockLiveStreaming: logPositionActions.lockAutoReloadScroll, - scrollUnlockLiveStreaming: logPositionActions.unlockAutoReloadScroll, - }) -); - -export const WithLogPosition = asChildFunctionRenderer(withLogPosition, { - onCleanup: ({ stopLiveStreaming }) => stopLiveStreaming(), -}); - -/** - * Url State - */ - -interface LogPositionUrlState { - position?: ReturnType; - streamLive?: ReturnType; -} - -export const WithLogPositionUrlState = () => ( - - {({ - jumpToTargetPosition, - jumpToTargetPositionTime, - startLiveStreaming, - stopLiveStreaming, - urlState, - }) => ( - { - if (newUrlState && newUrlState.position) { - jumpToTargetPosition(newUrlState.position); - } - if (newUrlState && newUrlState.streamLive) { - startLiveStreaming(); - } else if ( - newUrlState && - typeof newUrlState.streamLive !== 'undefined' && - !newUrlState.streamLive - ) { - stopLiveStreaming(); - } - }} - onInitialize={initialUrlState => { - if (initialUrlState && initialUrlState.position) { - jumpToTargetPosition(initialUrlState.position); - } else { - jumpToTargetPositionTime(Date.now()); - } - if (initialUrlState && initialUrlState.streamLive) { - startLiveStreaming(); - } - }} - /> - )} - -); - -const selectPositionUrlState = createSelector( - logPositionSelectors.selectVisibleMidpointOrTarget, - logPositionSelectors.selectIsAutoReloading, - (position, streamLive) => ({ - position: position ? pickTimeKey(position) : null, - streamLive, - }) -); - -const mapToUrlState = (value: any): LogPositionUrlState | undefined => - value - ? { - position: mapToPositionUrlState(value.position), - streamLive: mapToStreamLiveUrlState(value.streamLive), - } - : undefined; - -const mapToPositionUrlState = (value: any) => - value && typeof value.time === 'number' && typeof value.tiebreaker === 'number' - ? pickTimeKey(value) - : undefined; - -const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); - -export const replaceLogPositionInQueryString = (time: number) => - Number.isNaN(time) - ? (value: string) => value - : replaceStateKeyInQueryString('logPosition', { - position: { - time, - tiebreaker: 0, - }, - }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_search_controls_props.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_search_controls_props.ts deleted file mode 100644 index e387da9575426c..00000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_search_controls_props.ts +++ /dev/null @@ -1,35 +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. - */ - -/** - * Temporary Workaround - * This is not a well-designed container. It only exists to enable quick - * migration of the redux-based logging ui into the infra-ui codebase. It will - * be removed during the refactoring to graphql/apollo. - */ -import { connect } from 'react-redux'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; - -import { - // searchActions, - // searchResultsSelectors, - // sharedSelectors, - logPositionActions, - State, -} from '../../store'; - -export const withLogSearchControlsProps = connect( - (state: State) => ({ - // isLoadingSearchResults: searchResultsSelectors.selectIsLoadingSearchResults(state), - // nextSearchResult: sharedSelectors.selectNextSearchResultKey(state), - // previousSearchResult: sharedSelectors.selectPreviousSearchResultKey(state), - }), - bindPlainActionCreators({ - // clearSearch: searchActions.clearSearch, - jumpToTarget: logPositionActions.jumpToTargetPosition, - // search: searchActions.search, - }) -); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index 9b20676486af29..6da9cd7513cbab 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -10,7 +10,6 @@ import { LogEntry, LogEntryHighlight } from '../../utils/log_entry'; import { RendererFunction } from '../../utils/typed_react'; // deep inporting to avoid a circular import problem import { LogHighlightsState } from './log_highlights/log_highlights'; -import { LogPositionState } from './log_position'; import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries'; import { UniqueTimeKey } from '../../../common/time'; @@ -24,18 +23,17 @@ export const WithStreamItems: React.FunctionComponent<{ >; }> = ({ children }) => { const [logEntries, logEntriesCallbacks] = useContext(LogEntriesState.Context); - const { isAutoReloading } = useContext(LogPositionState.Context); const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); const items = useMemo( () => - logEntries.isReloading && !isAutoReloading + logEntries.isReloading ? [] : logEntries.entries.map(logEntry => createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) ), - [isAutoReloading, logEntries.entries, logEntries.isReloading, logEntryHighlightsById] + [logEntries.entries, logEntries.isReloading, logEntryHighlightsById] ); return children({ diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx index dfe4fb05d669a8..5eaa2850aebdb2 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx @@ -20,7 +20,7 @@ import { WithSource } from '../../containers/with_source'; import { Source } from '../../containers/source'; import { MetricsExplorerPage } from './metrics_explorer'; import { SnapshotPage } from './snapshot'; -import { SettingsPage } from '../shared/settings'; +import { MetricsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { SourceLoadingPage } from '../../components/source_loading_page'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; @@ -106,7 +106,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { )} /> - + diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/settings.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/settings.tsx new file mode 100644 index 00000000000000..d75af7879d17a0 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/settings.tsx @@ -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 React from 'react'; +import { SourceConfigurationSettings } from '../../components/source_configuration/source_configuration_settings'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; + +export const MetricsSettingsPage = () => { + const uiCapabilities = useKibana().services.application?.capabilities; + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx index a828cd207aa5ba..a5780f44050e13 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx @@ -23,12 +23,21 @@ export const SnapshotToolbar = () => ( - {({ changeMetric, changeNodeType, changeGroupBy, nodeType }) => ( + {({ + changeMetric, + changeNodeType, + changeGroupBy, + changeAccount, + changeRegion, + nodeType, + }) => ( )} diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index bfa1ede4236aa4..b3fcaff75aafdd 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom'; import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter'; -import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position'; +import { replaceLogPositionInQueryString } from '../../containers/logs/log_position'; import { replaceSourceIdInQueryString } from '../../containers/source_id'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 73d0633cb901b2..99b558cf1cafab 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -12,7 +12,7 @@ import { Redirect, RouteComponentProps } from 'react-router-dom'; import { LoadingPage } from '../../components/loading_page'; import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter'; -import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position'; +import { replaceLogPositionInQueryString } from '../../containers/logs/log_position'; import { replaceSourceIdInQueryString } from '../../containers/source_id'; import { InfraNodeType, SourceConfigurationFields } from '../../graphql/types'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index a8a75f99253c2b..f38f066b5323fd 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -17,7 +17,7 @@ import { SourceLoadingPage } from '../../components/source_loading_page'; import { SourceErrorPage } from '../../components/source_error_page'; import { Source, useSource } from '../../containers/source'; import { StreamPage } from './stream'; -import { SettingsPage } from '../shared/settings'; +import { LogsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { useLogAnalysisCapabilities, @@ -107,7 +107,7 @@ export const LogsPage = ({ match }: RouteComponentProps) => { - + { +export const LogsSettingsPage = () => { const uiCapabilities = useKibana().services.application?.capabilities; return ( ); }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index aff2a3b431067a..f789c16b67d9c8 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -21,14 +21,13 @@ import { WithFlyoutOptionsUrlState, } from '../../../containers/logs/log_flyout'; import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minimap'; -import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position'; -import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { LogPositionState } from '../../../containers/logs/log_position'; import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview'; import { WithStreamItems } from '../../../containers/logs/with_stream_items'; import { Source } from '../../../containers/source'; import { LogsToolbar } from './page_toolbar'; -import { LogHighlightsBridge, LogHighlightsState } from '../../../containers/logs/log_highlights'; +import { LogHighlightsState } from '../../../containers/logs/log_highlights'; export const LogsPageLogsContent: React.FunctionComponent = () => { const { source, sourceId, version } = useContext(Source.Context); @@ -44,110 +43,91 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { } = useContext(LogFlyoutState.Context); const { logSummaryHighlights } = useContext(LogHighlightsState.Context); const { applyLogFilterQuery } = useContext(LogFilterState.Context); + const { + isAutoReloading, + targetPosition, + visibleMidpointTime, + visibleTimeInterval, + reportVisiblePositions, + jumpToTargetPosition, + stopLiveStreaming, + } = useContext(LogPositionState.Context); return ( <> - - - - {({ jumpToTargetPosition, stopLiveStreaming }) => - flyoutVisible ? ( - { - jumpToTargetPosition(timeKey); - setSurroundingLogsId(flyoutItemId); - stopLiveStreaming(); - }} - setFlyoutVisibility={setFlyoutVisibility} - flyoutItem={flyoutItem} - loading={isLoading} - /> - ) : null - } - + {flyoutVisible ? ( + { + jumpToTargetPosition(timeKey); + setSurroundingLogsId(flyoutItemId); + stopLiveStreaming(); + }} + setFlyoutVisibility={setFlyoutVisibility} + flyoutItem={flyoutItem} + loading={isLoading} + /> + ) : null} - + {({ - isAutoReloading, - jumpToTargetPosition, - reportVisiblePositions, - targetPosition, - scrollLockLiveStreaming, - scrollUnlockLiveStreaming, - isScrollLocked, + currentHighlightKey, + hasMoreAfterEnd, + hasMoreBeforeStart, + isLoadingMore, + isReloading, + items, + lastLoadedTime, + fetchNewerEntries, }) => ( - - {({ - currentHighlightKey, - hasMoreAfterEnd, - hasMoreBeforeStart, - isLoadingMore, - isReloading, - items, - lastLoadedTime, - fetchNewerEntries, - }) => ( - - )} - + )} - + + {({ measureRef, bounds: { height = 0 }, content: { width = 0 } }) => { return ( {({ buckets }) => ( - - {({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => ( - - {({ isReloading }) => ( - 0 - ? logSummaryHighlights[0].buckets - : [] - } - target={visibleMidpointTime} - /> - )} - + + {({ isReloading }) => ( + 0 ? logSummaryHighlights[0].buckets : [] + } + target={visibleMidpointTime} + /> )} - + )} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx index 63425368b040f2..d19f27bf1286ea 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx @@ -9,7 +9,7 @@ import React, { useContext } from 'react'; import { LogFlyout } from '../../../containers/logs/log_flyout'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights'; -import { LogPositionState } from '../../../containers/logs/log_position'; +import { LogPositionState, WithLogPositionUrlState } from '../../../containers/logs/log_position'; import { LogFilterState, WithLogFilterUrlState } from '../../../containers/logs/log_filter'; import { LogEntriesState } from '../../../containers/logs/log_entries'; @@ -28,17 +28,23 @@ const LogFilterStateProvider: React.FC = ({ children }) => { const LogEntriesStateProvider: React.FC = ({ children }) => { const { sourceId } = useContext(Source.Context); - const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext( - LogPositionState.Context - ); + const { + targetPosition, + pagesBeforeStart, + pagesAfterEnd, + isAutoReloading, + jumpToTargetPosition, + } = useContext(LogPositionState.Context); const { filterQuery } = useContext(LogFilterState.Context); + const entriesProps = { - timeKey, + timeKey: targetPosition, pagesBeforeStart, pagesAfterEnd, filterQuery, sourceId, isAutoReloading, + jumpToTargetPosition, }; return {children}; }; @@ -62,6 +68,7 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => { + {children} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 84be3eeaf238dd..000dfd1065f12f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -20,7 +20,7 @@ import { LogTimeControls } from '../../../components/logging/log_time_controls'; import { LogFlyout } from '../../../containers/logs/log_flyout'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; import { LogFilterState } from '../../../containers/logs/log_filter'; -import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { LogPositionState } from '../../../containers/logs/log_position'; import { Source } from '../../../containers/source'; import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion'; @@ -54,6 +54,13 @@ export const LogsToolbar = () => { goToPreviousHighlight, goToNextHighlight, } = useContext(LogHighlightsState.Context); + const { + visibleMidpointTime, + isAutoReloading, + jumpToTargetPositionTime, + startLiveStreaming, + stopLiveStreaming, + } = useContext(LogPositionState.Context); return ( @@ -114,26 +121,16 @@ export const LogsToolbar = () => { /> - - {({ - visibleMidpointTime, - isAutoReloading, - jumpToTargetPositionTime, - startLiveStreaming, - stopLiveStreaming, - }) => ( - { - startLiveStreaming(); - setSurroundingLogsId(null); - }} - stopLiveStreaming={stopLiveStreaming} - /> - )} - + { + startLiveStreaming(); + setSurroundingLogsId(null); + }} + stopLiveStreaming={stopLiveStreaming} + /> diff --git a/x-pack/legacy/plugins/infra/public/store/actions.ts b/x-pack/legacy/plugins/infra/public/store/actions.ts index ce0c0d8a4e01b4..8a5d1f6c668d0b 100644 --- a/x-pack/legacy/plugins/infra/public/store/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/actions.ts @@ -4,9 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - logPositionActions, - waffleFilterActions, - waffleTimeActions, - waffleOptionsActions, -} from './local'; +export { waffleFilterActions, waffleTimeActions, waffleOptionsActions } from './local'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/actions.ts index 1827005e5e5e13..1c79d5a515cd4e 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/actions.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { logPositionActions } from './log_position'; export { waffleFilterActions } from './waffle_filter'; export { waffleTimeActions } from './waffle_time'; export { waffleOptionsActions } from './waffle_options'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts deleted file mode 100644 index ad83b6fda1b04b..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts +++ /dev/null @@ -1,39 +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 actionCreatorFactory from 'typescript-fsa'; - -import { TimeKey } from '../../../../common/time'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/log_position'); - -export const jumpToTargetPosition = actionCreator('JUMP_TO_TARGET_POSITION'); - -export const jumpToTargetPositionTime = (time: number, fromAutoReload: boolean = false) => - jumpToTargetPosition({ - tiebreaker: 0, - time, - fromAutoReload, - }); - -export interface ReportVisiblePositionsPayload { - pagesAfterEnd: number; - pagesBeforeStart: number; - endKey: TimeKey | null; - middleKey: TimeKey | null; - startKey: TimeKey | null; - fromScroll: boolean; -} - -export const reportVisiblePositions = actionCreator( - 'REPORT_VISIBLE_POSITIONS' -); - -export const startAutoReload = actionCreator('START_AUTO_RELOAD'); -export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD'); - -export const lockAutoReloadScroll = actionCreator('LOCK_AUTO_RELOAD_SCROLL'); -export const unlockAutoReloadScroll = actionCreator('UNLOCK_AUTO_RELOAD_SCROLL'); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts deleted file mode 100644 index de9e806469b06d..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts +++ /dev/null @@ -1,53 +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 { Action } from 'redux'; -import { Epic, combineEpics } from 'redux-observable'; -import { timer } from 'rxjs'; -import { exhaustMap, filter, map, takeUntil, mapTo, withLatestFrom } from 'rxjs/operators'; - -import { - jumpToTargetPosition, - jumpToTargetPositionTime, - startAutoReload, - stopAutoReload, -} from './actions'; - -const LIVE_STREAM_INTERVAL = 5000; - -const createLiveStreamEpic = (): Epic< - Action, - Action, - State, - { selectIsAutoReloadingScrollLocked: (state: State) => boolean } -> => (action$, state$, { selectIsAutoReloadingScrollLocked }) => - action$.pipe( - filter(startAutoReload.match), - exhaustMap(() => - timer(0, LIVE_STREAM_INTERVAL).pipe( - withLatestFrom(state$), - filter(([, state]) => selectIsAutoReloadingScrollLocked(state) === false), - map(() => jumpToTargetPositionTime(Date.now(), true)), - takeUntil(action$.pipe(filter(stopAutoReload.match))) - ) - ) - ); - -const createLiveStreamScrollCancelEpic = (): Epic< - Action, - Action, - State, - { selectIsAutoReloadingLogEntries: (state: State) => boolean } -> => (action$, state$, { selectIsAutoReloadingLogEntries }) => - action$.pipe( - filter(action => jumpToTargetPosition.match(action) && !action.payload.fromAutoReload), - withLatestFrom(state$), - filter(([, state]) => selectIsAutoReloadingLogEntries(state)), - mapTo(stopAutoReload()) - ); - -export const createLogPositionEpic = () => - combineEpics(createLiveStreamEpic(), createLiveStreamScrollCancelEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts deleted file mode 100644 index 2ca8be8e40d868..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ /dev/null @@ -1,120 +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 { combineReducers } from 'redux'; -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { TimeKey } from '../../../../common/time'; -import { - jumpToTargetPosition, - reportVisiblePositions, - startAutoReload, - stopAutoReload, - lockAutoReloadScroll, - unlockAutoReloadScroll, -} from './actions'; - -interface ManualTargetPositionUpdatePolicy { - policy: 'manual'; -} - -interface IntervalTargetPositionUpdatePolicy { - policy: 'interval'; -} - -type TargetPositionUpdatePolicy = - | ManualTargetPositionUpdatePolicy - | IntervalTargetPositionUpdatePolicy; - -export interface LogPositionState { - targetPosition: TimeKey | null; - updatePolicy: TargetPositionUpdatePolicy; - visiblePositions: { - startKey: TimeKey | null; - middleKey: TimeKey | null; - endKey: TimeKey | null; - pagesAfterEnd: number; - pagesBeforeStart: number; - }; - controlsShouldDisplayTargetPosition: boolean; - autoReloadScrollLock: boolean; -} - -export const initialLogPositionState: LogPositionState = { - targetPosition: null, - updatePolicy: { - policy: 'manual', - }, - visiblePositions: { - endKey: null, - middleKey: null, - startKey: null, - pagesBeforeStart: Infinity, - pagesAfterEnd: Infinity, - }, - controlsShouldDisplayTargetPosition: false, - autoReloadScrollLock: false, -}; - -const targetPositionReducer = reducerWithInitialState(initialLogPositionState.targetPosition).case( - jumpToTargetPosition, - (state, target) => target -); - -const targetPositionUpdatePolicyReducer = reducerWithInitialState( - initialLogPositionState.updatePolicy -) - .case(startAutoReload, () => ({ - policy: 'interval', - })) - .case(stopAutoReload, () => ({ - policy: 'manual', - })); - -const visiblePositionReducer = reducerWithInitialState( - initialLogPositionState.visiblePositions -).case( - reportVisiblePositions, - (state, { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd }) => ({ - endKey, - middleKey, - startKey, - pagesBeforeStart, - pagesAfterEnd, - }) -); - -// Determines whether to use the target position or the visible midpoint when -// displaying a timestamp or time range in the toolbar and log minimap. When the -// user jumps to a new target, the final visible midpoint is indeterminate until -// all the new data has finished loading, so using this flag reduces the perception -// that the UI is jumping around inaccurately -const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState( - initialLogPositionState.controlsShouldDisplayTargetPosition -) - .case(jumpToTargetPosition, () => true) - .case(stopAutoReload, () => false) - .case(startAutoReload, () => true) - .case(reportVisiblePositions, (state, { fromScroll }) => { - if (fromScroll) return false; - return state; - }); - -const autoReloadScrollLockReducer = reducerWithInitialState( - initialLogPositionState.autoReloadScrollLock -) - .case(startAutoReload, () => false) - .case(stopAutoReload, () => false) - .case(lockAutoReloadScroll, () => true) - .case(unlockAutoReloadScroll, () => false); - -export const logPositionReducer = combineReducers({ - targetPosition: targetPositionReducer, - updatePolicy: targetPositionUpdatePolicyReducer, - visiblePositions: visiblePositionReducer, - controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, - autoReloadScrollLock: autoReloadScrollLockReducer, -}); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts deleted file mode 100644 index 30fd4d3f77b5cd..00000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ /dev/null @@ -1,69 +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 { createSelector } from 'reselect'; - -import { LogPositionState } from './reducer'; - -export const selectTargetPosition = (state: LogPositionState) => state.targetPosition; - -export const selectIsAutoReloading = (state: LogPositionState) => - state.updatePolicy.policy === 'interval'; - -export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock; - -export const selectFirstVisiblePosition = (state: LogPositionState) => - state.visiblePositions.startKey ? state.visiblePositions.startKey : null; - -export const selectMiddleVisiblePosition = (state: LogPositionState) => - state.visiblePositions.middleKey ? state.visiblePositions.middleKey : null; - -export const selectLastVisiblePosition = (state: LogPositionState) => - state.visiblePositions.endKey ? state.visiblePositions.endKey : null; - -export const selectPagesBeforeAndAfter = (state: LogPositionState) => - state.visiblePositions - ? { - pagesBeforeStart: state.visiblePositions.pagesBeforeStart, - pagesAfterEnd: state.visiblePositions.pagesAfterEnd, - } - : { pagesBeforeStart: null, pagesAfterEnd: null }; -export const selectControlsShouldDisplayTargetPosition = (state: LogPositionState) => - state.controlsShouldDisplayTargetPosition; - -export const selectVisibleMidpointOrTarget = createSelector( - selectMiddleVisiblePosition, - selectTargetPosition, - selectControlsShouldDisplayTargetPosition, - (middleVisiblePosition, targetPosition, displayTargetPosition) => { - if (displayTargetPosition) { - return targetPosition; - } else if (middleVisiblePosition) { - return middleVisiblePosition; - } else if (targetPosition) { - return targetPosition; - } else { - return null; - } - } -); - -export const selectVisibleMidpointOrTargetTime = createSelector( - selectVisibleMidpointOrTarget, - visibleMidpointOrTarget => (visibleMidpointOrTarget ? visibleMidpointOrTarget.time : null) -); - -export const selectVisibleTimeInterval = createSelector( - selectFirstVisiblePosition, - selectLastVisiblePosition, - (firstVisiblePosition, lastVisiblePosition) => - firstVisiblePosition && lastVisiblePosition - ? { - start: firstVisiblePosition.time, - end: lastVisiblePosition.time, - } - : null -); diff --git a/x-pack/legacy/plugins/infra/public/store/local/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/reducer.ts index 5cc839af4c7cc1..9e194a5d37f493 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/reducer.ts @@ -6,7 +6,6 @@ import { combineReducers } from 'redux'; -import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position'; import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter'; import { initialWaffleOptionsState, @@ -16,21 +15,18 @@ import { import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time'; export interface LocalState { - logPosition: LogPositionState; waffleFilter: WaffleFilterState; waffleTime: WaffleTimeState; waffleMetrics: WaffleOptionsState; } export const initialLocalState: LocalState = { - logPosition: initialLogPositionState, waffleFilter: initialWaffleFilterState, waffleTime: initialWaffleTimeState, waffleMetrics: initialWaffleOptionsState, }; export const localReducer = combineReducers({ - logPosition: logPositionReducer, waffleFilter: waffleFilterReducer, waffleTime: waffleTimeReducer, waffleMetrics: waffleOptionsReducer, diff --git a/x-pack/legacy/plugins/infra/public/store/local/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/selectors.ts index c367901353b3d1..56ffc53c2bc72a 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/selectors.ts @@ -5,17 +5,11 @@ */ import { globalizeSelectors } from '../../utils/typed_redux'; -import { logPositionSelectors as innerLogPositionSelectors } from './log_position'; import { LocalState } from './reducer'; import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter'; import { waffleOptionsSelectors as innerWaffleOptionsSelectors } from './waffle_options'; import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time'; -export const logPositionSelectors = globalizeSelectors( - (state: LocalState) => state.logPosition, - innerLogPositionSelectors -); - export const waffleFilterSelectors = globalizeSelectors( (state: LocalState) => state.waffleFilter, innerWaffleFilterSelectors diff --git a/x-pack/legacy/plugins/infra/public/store/selectors.ts b/x-pack/legacy/plugins/infra/public/store/selectors.ts index d98e8ae9a0aae4..f4011c232cba45 100644 --- a/x-pack/legacy/plugins/infra/public/store/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/selectors.ts @@ -6,7 +6,6 @@ import { globalizeSelectors } from '../utils/typed_redux'; import { - logPositionSelectors as localLogPositionSelectors, waffleFilterSelectors as localWaffleFilterSelectors, waffleOptionsSelectors as localWaffleOptionsSelectors, waffleTimeSelectors as localWaffleTimeSelectors, @@ -18,7 +17,6 @@ import { State } from './reducer'; const selectLocal = (state: State) => state.local; -export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPositionSelectors); export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index 2781b0b2cef3f3..cae0622c5e4a15 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -9,14 +9,7 @@ import { createEpicMiddleware } from 'redux-observable'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { - createRootEpic, - initialState, - logPositionSelectors, - reducer, - State, - waffleTimeSelectors, -} from '.'; +import { createRootEpic, initialState, reducer, State, waffleTimeSelectors } from '.'; import { InfraApolloClient, InfraObservableApi } from '../lib/lib'; declare global { @@ -36,10 +29,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) const middlewareDependencies = { postToApi$: observableApi.pipe(map(({ post }) => post)), apolloClient$: apolloClient, - selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, - selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, - selectLogTargetPosition: logPositionSelectors.selectTargetPosition, - selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget, selectWaffleTimeUpdatePolicyInterval: waffleTimeSelectors.selectTimeUpdatePolicyInterval, }; diff --git a/x-pack/legacy/plugins/infra/types/eui.d.ts b/x-pack/legacy/plugins/infra/types/eui.d.ts index afcb445a66adb6..e73a73076923d4 100644 --- a/x-pack/legacy/plugins/infra/types/eui.d.ts +++ b/x-pack/legacy/plugins/infra/types/eui.d.ts @@ -34,7 +34,7 @@ declare module '@elastic/eui' { items: Array<{ id: string; name: string; - onClick: () => void; + onClick?: () => void; }>; }>; mobileTitle?: React.ReactNode; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js index 82e8c602a7a3eb..3e5c92dfc007f9 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js @@ -6,7 +6,7 @@ import React from 'react'; import { flatten } from 'lodash'; import { escapeKuery } from './escape_kuery'; -import { sortPrefixFirst } from 'ui/utils/sort_prefix_first'; +import { sortPrefixFirst } from './sort_prefix_first'; import { isFilterable } from '../../../../../../src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts new file mode 100644 index 00000000000000..7db05a60377da8 --- /dev/null +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { sortPrefixFirst } from './sort_prefix_first'; + +describe('sortPrefixFirst', () => { + test('should return the original unmodified array if no prefix is provided', () => { + const array = ['foo', 'bar', 'baz']; + const result = sortPrefixFirst(array); + + expect(result).toBe(array); + expect(result).toEqual(['foo', 'bar', 'baz']); + }); + + test('should sort items that match the prefix first without modifying the original array', () => { + const array = ['foo', 'bar', 'baz']; + const result = sortPrefixFirst(array, 'b'); + + expect(result).not.toBe(array); + expect(result).toEqual(['bar', 'baz', 'foo']); + expect(array).toEqual(['foo', 'bar', 'baz']); + }); + + test('should not modify the order of the array other than matching prefix without modifying the original array', () => { + const array = ['foo', 'bar', 'baz', 'qux', 'quux']; + const result = sortPrefixFirst(array, 'b'); + + expect(result).not.toBe(array); + expect(result).toEqual(['bar', 'baz', 'foo', 'qux', 'quux']); + expect(array).toEqual(['foo', 'bar', 'baz', 'qux', 'quux']); + }); + + test('should sort objects by property if provided', () => { + const array = [ + { name: 'foo' }, + { name: 'bar' }, + { name: 'baz' }, + { name: 'qux' }, + { name: 'quux' }, + ]; + const result = sortPrefixFirst(array, 'b', 'name'); + + expect(result).not.toBe(array); + expect(result).toEqual([ + { name: 'bar' }, + { name: 'baz' }, + { name: 'foo' }, + { name: 'qux' }, + { name: 'quux' }, + ]); + expect(array).toEqual([ + { name: 'foo' }, + { name: 'bar' }, + { name: 'baz' }, + { name: 'qux' }, + { name: 'quux' }, + ]); + }); + + test('should handle numbers', () => { + const array = [1, 50, 5]; + const result = sortPrefixFirst(array, 5); + + expect(result).not.toBe(array); + expect(result).toEqual([50, 5, 1]); + }); + + test('should handle mixed case', () => { + const array = ['Date Histogram', 'Histogram']; + const prefix = 'histo'; + const result = sortPrefixFirst(array, prefix); + + expect(result).not.toBe(array); + expect(result).toEqual(['Histogram', 'Date Histogram']); + }); +}); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts new file mode 100644 index 00000000000000..123e440b752312 --- /dev/null +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.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 { partition } from 'lodash'; + +export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] { + if (!prefix) { + return array; + } + const lowerCasePrefix = ('' + prefix).toLowerCase(); + + const partitions = partition(array, entry => { + const value = ('' + (property ? entry[property] : entry)).toLowerCase(); + return value.startsWith(lowerCasePrefix); + }); + return [...partitions[0], ...partitions[1]]; +} diff --git a/x-pack/legacy/plugins/license_management/public/register_route.ts b/x-pack/legacy/plugins/license_management/public/register_route.ts index 994a888ac020a6..0fe29b543be5bb 100644 --- a/x-pack/legacy/plugins/license_management/public/register_route.ts +++ b/x-pack/legacy/plugins/license_management/public/register_route.ts @@ -52,7 +52,7 @@ if (licenseManagementUiEnabled) { }; const initializeTelemetry = ($injector: any) => { - const telemetryEnabled = $injector.get('telemetryEnabled'); + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); const Private = $injector.get('Private'); const telemetryOptInProvider = Private(TelemetryOptInProvider); setTelemetryOptInService(telemetryOptInProvider); diff --git a/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap b/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap index 4486899efb0018..3b14025e9cb7be 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap @@ -101,7 +101,7 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule values={ Object { "learnMoreLink": { const results: LoadEvaluateResult = { success: false, eval: null, error: null }; const defaultPredictionField = `${dependentVariable}_prediction`; @@ -392,7 +394,7 @@ export const loadEvalData = async ({ predictionFieldName ? predictionFieldName : defaultPredictionField }`; - if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { + if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && requiresKeyword === true) { predictedField = `${predictedField}.keyword`; } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx index ddf52943c2feb2..68ed2c08d0df18 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx @@ -8,6 +8,7 @@ import React, { FC, useState, useEffect, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { + EuiButtonEmpty, EuiDataGrid, EuiFlexGroup, EuiFlexItem, @@ -18,6 +19,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import { metadata } from 'ui/metadata'; import { ErrorCallout } from '../error_callout'; import { getDependentVar, @@ -35,8 +37,13 @@ import { ResultsSearchQuery, ANALYSIS_CONFIG_TYPE, } from '../../../../common/analytics'; +import { IIndexPattern } from '../../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { ES_FIELD_TYPES } from '../../../../../../../../../../../src/plugins/data/public'; import { LoadingPanel } from '../loading_panel'; import { getColumnData } from './column_data'; +import { useKibanaContext } from '../../../../../contexts/kibana'; +import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; +import { getIndexPatternIdFromName } from '../../../../../util/index_utils'; const defaultPanelWidth = 500; @@ -55,17 +62,19 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) const [docsCount, setDocsCount] = useState(null); const [error, setError] = useState(null); const [panelWidth, setPanelWidth] = useState(defaultPanelWidth); - // Column visibility const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }: { id: string }) => id) ); + const kibanaContext = useKibanaContext(); const index = jobConfig.dest.index; + const sourceIndex = jobConfig.source.index[0]; const dependentVariable = getDependentVar(jobConfig.analysis); const predictionFieldName = getPredictionFieldName(jobConfig.analysis); // default is 'ml' const resultsField = jobConfig.dest.results_field; + let requiresKeyword = false; const loadData = async ({ isTrainingClause, @@ -76,6 +85,31 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) }) => { setIsLoading(true); + try { + const indexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; + const indexPattern: IIndexPattern = await kibanaContext.indexPatterns.get(indexPatternId); + + if (indexPattern !== undefined) { + await newJobCapsService.initializeFromIndexPattern(indexPattern, false, false); + // If dependent_variable is of type keyword and text .keyword suffix is required for evaluate endpoint + const { fields } = newJobCapsService; + const depVarFieldType = fields.find(field => field.name === dependentVariable)?.type; + + // If it's a keyword type - check if it has a corresponding text type + if (depVarFieldType !== undefined && depVarFieldType === ES_FIELD_TYPES.KEYWORD) { + const field = newJobCapsService.getFieldById(dependentVariable.replace(/\.keyword$/, '')); + requiresKeyword = field !== null && field.type === ES_FIELD_TYPES.TEXT; + } else if (depVarFieldType !== undefined && depVarFieldType === ES_FIELD_TYPES.TEXT) { + // If text, check if has corresponding keyword type + const field = newJobCapsService.getFieldById(`${dependentVariable}.keyword`); + requiresKeyword = field !== null && field.type === ES_FIELD_TYPES.KEYWORD; + } + } + } catch (e) { + // Additional error handling due to missing field type is handled by loadEvalData + console.error('Unable to load new field types', error); // eslint-disable-line no-console + } + const evalData = await loadEvalData({ isTraining: false, index, @@ -85,6 +119,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) searchQuery, ignoreDefaultQuery, jobType: ANALYSIS_CONFIG_TYPE.CLASSIFICATION, + requiresKeyword, }); const docsCountResp = await loadDocsCount({ @@ -210,7 +245,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - + @@ -227,6 +262,25 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) {getTaskStateBadge(jobStatus)} + + + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink', + { + defaultMessage: 'Classification evaluation docs ', + } + )} + + {error !== null && ( @@ -294,28 +348,18 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - - - - - - - - - - - - - + + + - + = ({ jobConfig, jobStatus, searchQuery }) return ( - + @@ -238,6 +247,25 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) {getTaskStateBadge(jobStatus)} + + + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.classificationExploration.regressionDocsLink', + { + defaultMessage: 'Regression evaluation docs ', + } + )} + + diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx index e11fb615efd708..828c91052b30b9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import { metadata } from 'ui/metadata'; -const docsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-calendars.html`; +const docsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-calendars.html`; export const Description: FC = memo(({ children }) => { const title = i18n.translate( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx index 89cd5c252c3d62..566bd313dbc6e6 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import { metadata } from 'ui/metadata'; -const docsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-configuring-url.html`; +const docsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-configuring-url.html`; export const Description: FC = memo(({ children }) => { const title = i18n.translate( diff --git a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index a19a27d00e9b04..1591dbcbad6bfa 100644 --- a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -70,8 +70,8 @@ export const JobsListPage: FC = ({ isMlEnabledInSpace }) => { const [currentTabId, setCurrentTabId] = useState(tabs[0].id); // metadata.branch corresponds to the version used in documentation links. - const anomalyDetectionJobsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-jobs.html`; - const anomalyJobsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-dfanalytics.html`; + const anomalyDetectionJobsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-jobs.html`; + const anomalyJobsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-dfanalytics.html`; const anomalyDetectionDocsLabel = i18n.translate( 'xpack.ml.management.jobsList.anomalyDetectionDocsLabel', diff --git a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts index 9d5c33d6cfc5c0..d78c9298c60736 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts @@ -14,6 +14,7 @@ import { } from '../../../common/types/fields'; import { ES_FIELD_TYPES, + IIndexPattern, IndexPattern, IndexPatternsContract, } from '../../../../../../../src/plugins/data/public'; @@ -89,7 +90,7 @@ class NewJobCapsService { } public async initializeFromIndexPattern( - indexPattern: IndexPattern, + indexPattern: IIndexPattern, includeEventRateField = true, removeTextFields = true ) { diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap index 2d32650a4b788b..0b39841ed61e48 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap @@ -89,7 +89,7 @@ exports[`CalendarListsHeader renders header 1`] = ` Object { "br":
, "learnMoreLink": , "learnMoreLink": { return messages; } - const createJobsDocsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/create-jobs.html`; + const createJobsDocsUrl = `https://www.elastic.co/guide/en/machine-learning/{{version}}/create-jobs.html`; return (messages = { field_not_aggregatable: { @@ -26,7 +26,7 @@ export const getMessages = () => { }, }), url: - 'https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/ml-configuring-aggregation.html', + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', }, fields_not_aggregatable: { status: 'ERROR', @@ -34,7 +34,7 @@ export const getMessages = () => { defaultMessage: 'One of the detector fields is not an aggregatable field.', }), url: - 'https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/ml-configuring-aggregation.html', + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', }, cardinality_by_field: { status: 'WARNING', @@ -112,7 +112,7 @@ export const getMessages = () => { } ), url: - 'https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/ml-configuring-categories.html', + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-categories.html', }, categorization_filters_invalid: { status: 'ERROR', diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js index fff0b742925c84..ef7d3f1224fab4 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js @@ -209,7 +209,7 @@ describe('BulkUploader', () => { }, CHECK_DELAY); }); - it('refetches UsageCollectors if uploading to local cluster was not successful', done => { + it('stops refetching UsageCollectors if uploading to local cluster was not successful', async () => { const usageCollectorFetch = sinon .stub() .returns({ type: 'type_usage_collector_test', result: { testData: 12345 } }); @@ -227,12 +227,52 @@ describe('BulkUploader', () => { uploader._onPayload = async () => ({ took: 0, ignored: true, errors: false }); - uploader.start(collectors); - setTimeout(() => { - uploader.stop(); - expect(usageCollectorFetch.callCount).to.be.greaterThan(1); - done(); - }, CHECK_DELAY); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + + expect(uploader._holdSendingUsage).to.eql(true); + expect(usageCollectorFetch.callCount).to.eql(1); + }); + + it('fetches UsageCollectors once uploading to local cluster is successful again', async () => { + const usageCollectorFetch = sinon + .stub() + .returns({ type: 'type_usage_collector_test', result: { usageData: 12345 } }); + + const statsCollectorFetch = sinon + .stub() + .returns({ type: 'type_stats_collector_test', result: { statsData: 12345 } }); + + const collectors = new MockCollectorSet(server, [ + { + fetch: statsCollectorFetch, + isReady: () => true, + formatForBulkUpload: result => result, + isUsageCollector: false, + }, + { + fetch: usageCollectorFetch, + isReady: () => true, + formatForBulkUpload: result => result, + isUsageCollector: true, + }, + ]); + + const uploader = new BulkUploader({ ...server, interval: FETCH_INTERVAL }); + let bulkIgnored = true; + uploader._onPayload = async () => ({ took: 0, ignored: bulkIgnored, errors: false }); + + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + expect(uploader._holdSendingUsage).to.eql(true); + + bulkIgnored = false; + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + + expect(uploader._holdSendingUsage).to.eql(false); + expect(usageCollectorFetch.callCount).to.eql(2); + expect(statsCollectorFetch.callCount).to.eql(3); }); it('calls UsageCollectors if last reported exceeds during a _usageInterval', done => { diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js index 2d81cb23b6b3b5..5e0d8aa4be1fdc 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js @@ -40,8 +40,14 @@ export class BulkUploader { } this._timer = null; + // Hold sending and fetching usage until monitoring.bulk is successful. This means that we + // send usage data on the second tick. But would save a lot of bandwidth fetching usage on + // every tick when ES is failing or monitoring is disabled. + this._holdSendingUsage = false; this._interval = interval; this._lastFetchUsageTime = null; + // Limit sending and fetching usage to once per day once usage is successfully stored + // into the monitoring indices. this._usageInterval = TELEMETRY_COLLECTION_INTERVAL; this._log = { @@ -65,6 +71,29 @@ export class BulkUploader { }); } + filterCollectorSet(usageCollection) { + const successfulUploadInLastDay = + this._lastFetchUsageTime && this._lastFetchUsageTime + this._usageInterval > Date.now(); + + return usageCollection.getFilteredCollectorSet(c => { + // this is internal bulk upload, so filter out API-only collectors + if (c.ignoreForInternalUploader) { + return false; + } + // Only collect usage data at the same interval as telemetry would (default to once a day) + if (usageCollection.isUsageCollector(c)) { + if (this._holdSendingUsage) { + return false; + } + if (successfulUploadInLastDay) { + return false; + } + } + + return true; + }); + } + /* * Start the interval timer * @param {usageCollection} usageCollection object to use for initial the fetch/upload and fetch/uploading on interval @@ -72,31 +101,15 @@ export class BulkUploader { */ start(usageCollection) { this._log.info('Starting monitoring stats collection'); - const filterCollectorSet = _usageCollection => { - const successfulUploadInLastDay = - this._lastFetchUsageTime && this._lastFetchUsageTime + this._usageInterval > Date.now(); - - return _usageCollection.getFilteredCollectorSet(c => { - // this is internal bulk upload, so filter out API-only collectors - if (c.ignoreForInternalUploader) { - return false; - } - // Only collect usage data at the same interval as telemetry would (default to once a day) - if (successfulUploadInLastDay && _usageCollection.isUsageCollector(c)) { - return false; - } - return true; - }); - }; if (this._timer) { clearInterval(this._timer); } else { - this._fetchAndUpload(filterCollectorSet(usageCollection)); // initial fetch + this._fetchAndUpload(this.filterCollectorSet(usageCollection)); // initial fetch } this._timer = setInterval(() => { - this._fetchAndUpload(filterCollectorSet(usageCollection)); + this._fetchAndUpload(this.filterCollectorSet(usageCollection)); }, this._interval); } @@ -146,12 +159,17 @@ export class BulkUploader { const sendSuccessful = !result.ignored && !result.errors; if (!sendSuccessful && hasUsageCollectors) { this._lastFetchUsageTime = null; + this._holdSendingUsage = true; this._log.debug( 'Resetting lastFetchWithUsage because uploading to the cluster was not successful.' ); } - if (sendSuccessful && hasUsageCollectors) { - this._lastFetchUsageTime = Date.now(); + + if (sendSuccessful) { + this._holdSendingUsage = false; + if (hasUsageCollectors) { + this._lastFetchUsageTime = Date.now(); + } } this._log.debug(`Uploaded bulk stats payload to the local cluster`); } catch (err) { diff --git a/x-pack/legacy/plugins/reporting/index.test.js b/x-pack/legacy/plugins/reporting/index.test.js index f1b471071153c1..0d9a717bd7d816 100644 --- a/x-pack/legacy/plugins/reporting/index.test.js +++ b/x-pack/legacy/plugins/reporting/index.test.js @@ -16,6 +16,7 @@ jest.mock('os', () => { return os; }); +// eslint-disable-next-line jest/valid-describe const describeWithContext = describe.each([ [{ dev: false, dist: false }], [{ dev: true, dist: false }], diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 4383329fea0723..5116416b527a5e 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -27,8 +27,6 @@ export const DEFAULT_SEARCH_AFTER_PAGE_SIZE = 100; export const DEFAULT_ANOMALY_SCORE = 'siem:defaultAnomalyScore'; export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000; export const DEFAULT_SCALE_DATE_FORMAT = 'dateFormat:scaled'; -export const DEFAULT_KBN_VERSION = 'kbnVersion'; -export const DEFAULT_TIMEZONE_BROWSER = 'timezoneBrowser'; export const DEFAULT_FROM = 'now-24h'; export const DEFAULT_TO = 'now'; export const DEFAULT_INTERVAL_PAUSE = true; diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index cf9fffc6a14558..c5038626fdfc20 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { Server } from 'hapi'; import { Root } from 'joi'; -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext } from '../../../../src/core/server'; import { plugin } from './server'; import { savedObjectMappings } from './server/saved_objects'; @@ -43,7 +43,7 @@ export const siem = (kibana: any) => { description: i18n.translate('xpack.siem.securityDescription', { defaultMessage: 'Explore your SIEM App', }), - main: 'plugins/siem/app', + main: 'plugins/siem/legacy', euiIconType: 'securityAnalyticsApp', title: APP_NAME, listed: false, diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index bf5d6d3a3089cb..558ac013e5963b 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@types/lodash": "^4.14.110", "@types/js-yaml": "^3.12.1", - "@types/react-beautiful-dnd": "^11.0.3" + "@types/react-beautiful-dnd": "^11.0.4" }, "dependencies": { "lodash": "^4.17.15", diff --git a/x-pack/legacy/plugins/siem/public/apps/start_app.tsx b/x-pack/legacy/plugins/siem/public/app/app.tsx similarity index 96% rename from x-pack/legacy/plugins/siem/public/apps/start_app.tsx rename to x-pack/legacy/plugins/siem/public/app/app.tsx index 100f10aaf2e7bb..5f9199735d8c04 100644 --- a/x-pack/legacy/plugins/siem/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/siem/public/app/app.tsx @@ -25,7 +25,7 @@ import { DEFAULT_DARK_MODE } from '../../common/constants'; import { ErrorToastDispatcher } from '../components/error_toast_dispatcher'; import { compose } from '../lib/compose/kibana_compose'; import { AppFrontendLibs, AppApolloClient } from '../lib/lib'; -import { StartCore, StartPlugins } from './plugin'; +import { CoreStart, StartPlugins } from '../plugin'; import { PageRouter } from '../routes'; import { createStore } from '../store'; import { GlobalToaster, ManageGlobalToaster } from '../components/toasters'; @@ -89,10 +89,8 @@ const StartAppComponent: FC = libs => { const StartApp = memo(StartAppComponent); -export const ROOT_ELEMENT_ID = 'react-siem-root'; - interface SiemAppComponentProps { - core: StartCore; + core: CoreStart; plugins: StartPlugins; } diff --git a/x-pack/legacy/plugins/siem/public/app/index.tsx b/x-pack/legacy/plugins/siem/public/app/index.tsx new file mode 100644 index 00000000000000..01175a98d1e44e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/app/index.tsx @@ -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 React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { CoreStart, StartPlugins, AppMountParameters } from '../plugin'; +import { SiemApp } from './app'; + +export const renderApp = ( + core: CoreStart, + plugins: StartPlugins, + { element }: AppMountParameters +) => { + render(, element); + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/siem/public/apps/index.ts b/x-pack/legacy/plugins/siem/public/apps/index.ts deleted file mode 100644 index 0cc5c5584e1b76..00000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/index.ts +++ /dev/null @@ -1,18 +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 chrome from 'ui/chrome'; -import { npStart } from 'ui/new_platform'; -import { Plugin } from './plugin'; - -const { data, embeddable, inspector, uiActions } = npStart.plugins; -const startPlugins = { data, embeddable, inspector, uiActions }; - -new Plugin( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { opaqueId: Symbol('siem'), env: {} as any, config: { get: () => ({} as any) } }, - chrome -).start(npStart.core, startPlugins); diff --git a/x-pack/legacy/plugins/siem/public/apps/plugin.tsx b/x-pack/legacy/plugins/siem/public/apps/plugin.tsx deleted file mode 100644 index aa42504e07635f..00000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/plugin.tsx +++ /dev/null @@ -1,59 +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 from 'react'; -import { render } from 'react-dom'; -import { LegacyCoreStart, PluginInitializerContext } from 'src/core/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; -import { Chrome } from 'ui/chrome'; - -import { DEFAULT_KBN_VERSION, DEFAULT_TIMEZONE_BROWSER } from '../../common/constants'; -import { SiemApp } from './start_app'; -import template from './template.html'; - -export const ROOT_ELEMENT_ID = 'react-siem-root'; - -export type StartCore = LegacyCoreStart; -export type StartPlugins = Required< - Pick ->; -export type StartServices = StartCore & StartPlugins; - -export class Plugin { - constructor( - // @ts-ignore this is added to satisfy the New Platform typing constraint, - // but we're not leveraging any of its functionality yet. - private readonly initializerContext: PluginInitializerContext, - private readonly chrome: Chrome - ) { - this.chrome = chrome; - } - - public start(core: StartCore, plugins: StartPlugins) { - // TODO(rylnd): These are unknown by uiSettings by default - core.uiSettings.set(DEFAULT_KBN_VERSION, '8.0.0'); - core.uiSettings.set(DEFAULT_TIMEZONE_BROWSER, 'UTC'); - - // @ts-ignore improper type description - this.chrome.setRootTemplate(template); - const checkForRoot = () => { - return new Promise(resolve => { - const ready = !!document.getElementById(ROOT_ELEMENT_ID); - if (ready) { - resolve(); - } else { - setTimeout(() => resolve(checkForRoot()), 10); - } - }); - }; - checkForRoot().then(() => { - const node = document.getElementById(ROOT_ELEMENT_ID); - if (node) { - render(, node); - } - }); - } -} diff --git a/x-pack/legacy/plugins/siem/public/apps/template.html b/x-pack/legacy/plugins/siem/public/apps/template.html deleted file mode 100644 index 9f757b25ccecb6..00000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/template.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx index be449e3d422d97..2fb270c2840005 100644 --- a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx index 10d3c899562e8c..5404a1ac43844f 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; @@ -20,7 +19,7 @@ describe('arrows', () => { ); - expect(toJson(wrapper.find('ArrowBody'))).toMatchSnapshot(); + expect(wrapper.find('ArrowBody')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx b/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx index dfc7645c564d26..97b5eb04ac7bbb 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx @@ -5,7 +5,7 @@ */ import { EuiIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; /** Renders the body (non-pointy part) of an arrow */ diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx index 77a7296e368cfe..27e87d25e286f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx @@ -7,9 +7,8 @@ import { EuiFieldSearch } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; @@ -117,7 +116,7 @@ describe('Autocomplete', () => { value={''} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it is rendering with placeholder', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx index 6816bff24f1cd2..d99a909efad102 100644 --- a/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { PreferenceFormattedBytes } from '../formatted_bytes'; diff --git a/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx index fbe83623211b1a..94c6ecba68be52 100644 --- a/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DefaultDraggable } from '../draggables'; import { PreferenceFormattedBytes } from '../formatted_bytes'; diff --git a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx index b0c165fedfffc1..9cd0af062c54a6 100644 --- a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx index f8db7d754aab15..181d92dce06f99 100644 --- a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx @@ -5,7 +5,7 @@ */ import { EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 2b99efc05fd8cb..ac283790671d3a 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -5,7 +5,7 @@ */ import { ShallowWrapper, shallow } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { AreaChartBaseComponent, AreaChartComponent } from './areachart'; import { ChartSeriesData } from './common'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index ba07a3f3436d93..71f22efadc6ed2 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -18,6 +18,7 @@ import { import { getOr, get, isNull, isNumber } from 'lodash/fp'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; +import { useTimeZone } from '../../hooks'; import { chartDefaultSettings, ChartSeriesConfigs, @@ -26,7 +27,6 @@ import { getChartWidth, WrappedByAutoSizer, useTheme, - useBrowserTimeZone, } from './common'; // custom series styles: https://ela.st/areachart-styling @@ -71,7 +71,7 @@ export const AreaChartBaseComponent = ({ configs?: ChartSeriesConfigs | undefined; }) => { const theme = useTheme(); - const timeZone = useBrowserTimeZone(); + const timeZone = useTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const xAxisId = `group-${data[0].key}-x`; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index 506b1ceb5ed830..ac9c4d591232a6 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -5,7 +5,7 @@ */ import { shallow, ShallowWrapper } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index db84d7dbd2c18f..415cbeb7c24409 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { Chart, BarSeries, Axis, Position, ScaleType, Settings } from '@elastic/charts'; import { getOr, get, isNumber } from 'lodash/fp'; +import { useTimeZone } from '../../hooks'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; import { @@ -17,7 +18,6 @@ import { getChartHeight, getChartWidth, WrappedByAutoSizer, - useBrowserTimeZone, useTheme, } from './common'; @@ -44,7 +44,7 @@ export const BarChartBaseComponent = ({ configs?: ChartSeriesConfigs | undefined; }) => { const theme = useTheme(); - const timeZone = useBrowserTimeZone(); + const timeZone = useTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const tickSize = getOr(0, 'configs.axis.tickSize', chartConfigs); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx index a4be390019916d..78cce72f0a0d39 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx @@ -15,10 +15,9 @@ import { SettingsSpecProps, TickFormatter, } from '@elastic/charts'; -import moment from 'moment-timezone'; import styled from 'styled-components'; import { useUiSetting } from '../../lib/kibana'; -import { DEFAULT_DATE_FORMAT_TZ, DEFAULT_DARK_MODE } from '../../../common/constants'; +import { DEFAULT_DARK_MODE } from '../../../common/constants'; export const defaultChartHeight = '100%'; export const defaultChartWidth = '100%'; @@ -108,11 +107,6 @@ export const chartDefaultSettings = { debug: false, }; -export const useBrowserTimeZone = () => { - const kibanaTimezone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); - return kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; -}; - export const getChartHeight = (customHeight?: number, autoSizerHeight?: number): string => { const height = customHeight || autoSizerHeight; return height ? `${height}px` : defaultChartHeight; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx index a5eac381f92150..eae0fc4ff422b8 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -44,7 +43,7 @@ describe('UtilityBar', () => { ); - expect(toJson(wrapper.find('UtilityBar'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBar')).toMatchSnapshot(); }); test('it applies border styles when border is true', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx index 2610fb44532f59..2a8a71955a986a 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -19,7 +18,7 @@ describe('UtilityBarAction', () => { ); - expect(toJson(wrapper.find('UtilityBarAction'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarAction')).toMatchSnapshot(); }); test('it renders a popover', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx index 59ef7021d40493..e18e7d5e0b524f 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -21,6 +20,6 @@ describe('UtilityBarGroup', () => { ); - expect(toJson(wrapper.find('UtilityBarGroup'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarGroup')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx index baa4331ced8f88..f849fa4b4ee46d 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -23,6 +22,6 @@ describe('UtilityBarSection', () => { ); - expect(toJson(wrapper.find('UtilityBarSection'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarSection')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx index 794f207fd88e36..230dd80b1a86b7 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -19,6 +18,6 @@ describe('UtilityBarText', () => { ); - expect(toJson(wrapper.find('UtilityBarText'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarText')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/direction/index.tsx b/x-pack/legacy/plugins/siem/public/components/direction/index.tsx index 9295e055f918d7..ad1e63dbd7e6a1 100644 --- a/x-pack/legacy/plugins/siem/public/components/direction/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/direction/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { NetworkDirectionEcs } from '../../graphql/types'; import { DraggableBadge } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx index 1a8af9d99193a6..9e8bde8d9ff92c 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; @@ -28,7 +27,7 @@ describe('DragDropContextWrapper', () => { ); - expect(toJson(wrapper.find('DragDropContextWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DragDropContextWrapper')).toMatchSnapshot(); }); test('it renders the children', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx index 4b546bca1f72eb..e846c923c5cbe3 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; @@ -33,7 +32,7 @@ describe('DraggableWrapper', () => { ); - expect(toJson(wrapper.find('DraggableWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DraggableWrapper')).toMatchSnapshot(); }); test('it renders the children passed to the render prop', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx index 056669673bb9e2..bd2f01721290fb 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; @@ -33,7 +32,7 @@ describe('DroppableWrapper', () => { ); - expect(toJson(wrapper.find('DroppableWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DroppableWrapper')).toMatchSnapshot(); }); test('it renders the children when a render prop is not provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx index c660ac6adaa71d..821ef9be10e8d0 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx @@ -5,7 +5,7 @@ */ import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import { Droppable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx index 90d8ad463b476e..ba0d53210bace5 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx @@ -5,7 +5,7 @@ */ import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; const Field = styled.div` diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx index f1ed533bef545f..76335e3c723061 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { getEmptyString } from '../empty_value'; @@ -34,7 +33,7 @@ describe('draggables', () => { {'A child of this'} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the default Badge', () => { @@ -50,7 +49,7 @@ describe('draggables', () => { {'A child of this'} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx index 323dee53f1910f..57f047416ec1ca 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge, EuiBadgeProps, EuiToolTip, IconType } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Omit } from '../../../common/utility_types'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx index 140a625bc53fee..0dbc60ad9ae523 100644 --- a/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { ONE_MILLISECOND_AS_NANOSECONDS } from '../formatted_duration/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/duration/index.tsx b/x-pack/legacy/plugins/siem/public/components/duration/index.tsx index 15e6246f1f1ad8..76712b789ffbe5 100644 --- a/x-pack/legacy/plugins/siem/public/components/duration/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/duration/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DefaultDraggable } from '../draggables'; import { FormattedDuration } from '../formatted_duration'; diff --git a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx index 7c515862b0d921..1786905a4bb48a 100644 --- a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx index 884d5bc348d6f3..2dc3d8828675f8 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { Embeddable } from './embeddable'; @@ -18,6 +17,6 @@ describe('Embeddable', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx index aa247b69eb4eb2..3b8e137618ab0c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -15,7 +14,7 @@ describe('EmbeddableHeader', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the title', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx index 007916595fd6ad..c752273777d2f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { useIndexPatterns } from '../../hooks/use_index_patterns'; import { EmbeddedMapComponent } from './embedded_map'; @@ -41,6 +40,6 @@ describe('EmbeddedMapComponent', () => { startDate={new Date('2019-08-28T05:50:47.877Z').getTime()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx index d04329edff4756..4f617644a1fe14 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { IndexPatternsMissingPromptComponent } from './index_patterns_missing_prompt'; @@ -15,6 +14,6 @@ jest.mock('../../lib/kibana'); describe('IndexPatternsMissingPrompt', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx index 798e3d2c10f97f..a4f95d2e299adc 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx @@ -6,7 +6,7 @@ import { EuiButton, EuiCode, EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import chrome from 'ui/chrome'; import { useKibana } from '../../lib/kibana'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx index c43ab1ff4a036f..824c717427763c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { LineToolTipContentComponent } from './line_tool_tip_content'; import { FeatureProperty } from '../types'; import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; @@ -27,6 +26,6 @@ describe('LineToolTipContent', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx index 13eefb252fb04b..2daaeb53e45f2c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MapToolTipComponent } from './map_tool_tip'; import { MapFeature } from '../types'; @@ -19,7 +18,7 @@ jest.mock('../../search_bar', () => ({ describe('MapToolTip', () => { test('placeholder component renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('full component renders correctly against snapshot', () => { @@ -46,6 +45,6 @@ describe('MapToolTip', () => { loadFeatureGeometry={loadFeatureGeometry} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index 929b4983b5fd76..8741cfaa26ca6d 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FeatureProperty } from '../types'; import { getRenderedFieldValue, PointToolTipContentComponent } from './point_tool_tip_content'; import { TestProviders } from '../../../mock'; @@ -49,7 +48,7 @@ describe('PointToolTipContent', () => { /> ); - expect(toJson(wrapper.find('PointToolTipContentComponent'))).toMatchSnapshot(); + expect(wrapper.find('PointToolTipContentComponent')).toMatchSnapshot(); }); test('renders array filter correctly', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx index 4c77570cfbc9f2..7351ea0a183c36 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ToolTipFooterComponent } from './tooltip_footer'; describe('ToolTipFilter', () => { @@ -27,7 +26,7 @@ describe('ToolTipFilter', () => { totalFeatures={100} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('Lower bounds', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/empty_page/index.test.tsx index 67b0c5ea64b515..6a14c12cee0f85 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { EmptyPage } from './index'; @@ -18,5 +17,5 @@ test('renders correctly', () => { title="My Super Title" /> ); - expect(toJson(EmptyComponent)).toMatchSnapshot(); + expect(EmptyComponent).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/empty_value/empty_value.test.tsx b/x-pack/legacy/plugins/siem/public/components/empty_value/empty_value.test.tsx index bd056c04acc894..fc1d30907ab092 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_value/empty_value.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/empty_value/empty_value.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { ThemeProvider } from 'styled-components'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; @@ -25,7 +24,7 @@ describe('EmptyValue', () => { test('it renders against snapshot', () => { const wrapper = shallow(

{getEmptyString()}

); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('#getEmptyValue', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx index 6233fcfe7c823c..6b90d9ccd08c48 100644 --- a/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Provider } from 'react-redux'; import { apolloClientObservable, mockGlobalState } from '../../mock'; @@ -30,7 +29,7 @@ describe('Error Toast Dispatcher', () => { ); - expect(toJson(wrapper.find('Connect(ErrorToastDispatcherComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(ErrorToastDispatcherComponent)')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx index 18404c2263b1c5..1962850425baac 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx @@ -15,7 +15,7 @@ import { EuiText, EuiToolTip, } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx index d97da7797bb451..162fc8fd8bb343 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item'; import { TestProviders } from '../../mock/test_providers'; @@ -34,7 +33,7 @@ describe('EventDetails', () => { toggleColumn={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx index 25f95bfa1d383f..5b18e2d9b1fbce 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item'; import { TestProviders } from '../../mock/test_providers'; @@ -14,8 +14,6 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { defaultHeaders } from '../../mock/header'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/kibana'); - describe('EventFieldsBrowser', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx index 429fc94b2f2d32..0cf158c8ea90bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockDetailItemData } from '../../mock'; @@ -16,7 +15,7 @@ describe('JSON View', () => { describe('rendering', () => { test('should match snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx index 519f56adff2d20..9897e319e0487b 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx @@ -6,7 +6,7 @@ import { EuiCodeEditor } from '@elastic/eui'; import { set } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DetailItem } from '../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index b44d83c27a60d3..3cef3e98c2f0a7 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx @@ -18,8 +18,6 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/kibana'); - const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock; jest.mock('../../containers/detection_engine/rules/fetch_index_patterns'); mockUseFetchIndexPatterns.mockImplementation(() => [ diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx index 27c3abf7f6824c..1e225dabb25410 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx @@ -17,8 +17,6 @@ import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/f import { mockBrowserFields } from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; -jest.mock('../../lib/kibana'); - const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock; jest.mock('../../containers/detection_engine/rules/fetch_index_patterns'); mockUseFetchIndexPatterns.mockImplementation(() => [ diff --git a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx index 01317e754ad35c..24118ace6796f2 100644 --- a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx index bba32e72abc371..147d2e2d541f52 100644 --- a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx @@ -5,7 +5,7 @@ */ import { EuiIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; const LinkIcon = styled(EuiIcon)` diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx index e45f5dacb36a26..88d03d8db67611 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FlowTarget, GetIpOverviewQuery, HostEcsFields } from '../../graphql/types'; import { TestProviders } from '../../mock'; @@ -37,7 +36,7 @@ describe('Field Renderers', () => { locationRenderer(['source.geo.city_name', 'source.geo.region_name'], mockData.complete) ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when no fields provided', () => { @@ -61,7 +60,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(dateRenderer(mockData.complete.source!.firstSeen)); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when invalid field provided', () => { @@ -79,7 +78,7 @@ describe('Field Renderers', () => { autonomousSystemRenderer(mockData.complete.source!.autonomousSystem!, FlowTarget.source) ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when non-string field provided', () => { @@ -111,7 +110,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(hostNameRenderer(mockData.complete.host, '10.10.10.10')); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when non-matching IP is provided', () => { @@ -154,7 +153,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(hostNameRenderer(mockData.complete.host, '10.10.10.10')); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when non-matching IP is provided', () => { @@ -188,7 +187,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(whoisRenderer('10.10.10.10')); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); @@ -196,7 +195,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow({reputationRenderer('10.10.10.10')}); - expect(toJson(wrapper.find('DragDropContext'))).toMatchSnapshot(); + expect(wrapper.find('DragDropContext')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx index 2d3accbcb55c86..361a0789135e41 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx index 3165580f435a2b..d6972625821cf3 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx @@ -5,7 +5,7 @@ */ import { EuiInMemoryTable, EuiTitle } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx index 4e16997ba92f6c..38eaf43977fa21 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx index 7b8451db2212f1..9d2a7da9b2d00f 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx @@ -5,7 +5,7 @@ */ import { EuiInMemoryTable } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx index ce66a2d8d79195..cbead878f525db 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx index e0628a410921e5..792e0342a6d592 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx index 49255abc83dd58..cd14cef328a7e6 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx index c43d5833fe1da2..9214fd5f2540ce 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx index 6034f5a4764432..4d0c707c469105 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx @@ -5,7 +5,7 @@ */ import { omit } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx index 17553c79b643cd..778e9d3d3c7449 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx @@ -8,7 +8,7 @@ import { EuiCheckbox, EuiIcon, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { uniqBy } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx index 59ee2efa1f0061..1437af7a30adb7 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx index 68ba2e2774314e..f3ec87a96d46b6 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx index 170cf324ca6d8a..fba6e22e4b21f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx index 7e36a028961c48..42689065354d00 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx index 8acb19970c268c..45b331f133e85e 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx @@ -12,7 +12,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx index 8a01a01b1daae8..24e4cd77b20d3f 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx index 3958cd463d56ef..c8cde5fa02a519 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx @@ -212,9 +212,7 @@ export const StatefulFieldsBrowserComponent = React.memo { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx index 47f0aef4b48060..f984b534c188d7 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FlowDirection } from '../../graphql/types'; @@ -25,7 +24,7 @@ describe('Select Flow Direction', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx index b1f757841e54b1..373cc3cdc4a92d 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { clone } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ActionCreator } from 'typescript-fsa'; import { FlowDirection, FlowTarget } from '../../graphql/types'; @@ -31,7 +30,7 @@ describe('FlowTargetSelect Component', () => { test('it renders the FlowTargetSelect', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx index fee4f25f9e2559..6ec5912872467f 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx @@ -6,7 +6,7 @@ import { EuiNotificationBadge, EuiIcon, EuiButton } from '@elastic/eui'; import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx index be7e8fac70bf53..83b842956e10ea 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { set } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ActionCreator } from 'typescript-fsa'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../mock'; @@ -35,7 +34,7 @@ describe('Flyout', () => { /> ); - expect(toJson(wrapper.find('Flyout'))).toMatchSnapshot(); + expect(wrapper.find('Flyout')).toMatchSnapshot(); }); test('it renders the default flyout state as a button', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index 2d347830d5b1b5..528f02f0a845b7 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -6,7 +6,7 @@ import { EuiBadge } from '@elastic/eui'; import { defaultTo, getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx index 246261035508bd..365f99c6667b8f 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock'; import { flyoutHeaderHeight } from '..'; @@ -16,8 +15,6 @@ const testFlyoutHeight = 980; const testWidth = 640; const usersViewing = ['elastic']; -jest.mock('../../../lib/kibana'); - describe('Pane', () => { test('renders correctly against snapshot', () => { const EmptyComponent = shallow( @@ -34,7 +31,7 @@ describe('Pane', () => { ); - expect(toJson(EmptyComponent.find('Pane'))).toMatchSnapshot(); + expect(EmptyComponent.find('Pane')).toMatchSnapshot(); }); test('it should NOT let the flyout expand to take up the full width of the element that contains it', () => { @@ -53,7 +50,7 @@ describe('Pane', () => { ); - expect(wrapper.find('[data-test-subj="eui-flyout"]').get(0).props.maxWidth).toEqual('95%'); + expect(wrapper.find('Resizable').get(0).props.maxWidth).toEqual('95vw'); }); test('it applies timeline styles to the EuiFlyout', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx index f2f0cf4f980f33..00ac15092a6ec1 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx @@ -5,13 +5,14 @@ */ import { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiToolTip } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { connect } from 'react-redux'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; +import { Resizable, ResizeCallback } from 're-resizable'; +import { throttle } from 'lodash/fp'; -import { OnResize, Resizeable } from '../../resize_handle'; -import { TimelineResizeHandle } from '../../resize_handle/styled_handles'; +import { TimelineResizeHandle } from './timeline_resize_handle'; import { FlyoutHeader } from '../header'; import * as i18n from './translations'; @@ -41,10 +42,10 @@ interface DispatchProps { type Props = OwnProps & DispatchProps; -const EuiFlyoutContainer = styled.div<{ headerHeight: number; width: number }>` +const EuiFlyoutContainer = styled.div<{ headerHeight: number }>` .timeline-flyout { min-width: 150px; - width: ${({ width }) => `${width}px`}; + width: auto; } .timeline-flyout-header { align-items: center; @@ -65,8 +66,6 @@ const EuiFlyoutContainer = styled.div<{ headerHeight: number; width: number }>` } `; -EuiFlyoutContainer.displayName = 'EuiFlyoutContainer'; - const FlyoutHeaderContainer = styled.div` align-items: center; display: flex; @@ -75,88 +74,95 @@ const FlyoutHeaderContainer = styled.div` width: 100%; `; -FlyoutHeaderContainer.displayName = 'FlyoutHeaderContainer'; - // manually wrap the close button because EuiButtonIcon can't be a wrapped `styled` const WrappedCloseButton = styled.div` margin-right: 5px; `; -WrappedCloseButton.displayName = 'WrappedCloseButton'; - -const FlyoutHeaderWithCloseButton = React.memo<{ +const FlyoutHeaderWithCloseButtonComponent: React.FC<{ onClose: () => void; timelineId: string; usersViewing: string[]; -}>( - ({ onClose, timelineId, usersViewing }) => ( - - - - - - - - - ), +}> = ({ onClose, timelineId, usersViewing }) => ( + + + + + + + + +); + +const FlyoutHeaderWithCloseButton = React.memo( + FlyoutHeaderWithCloseButtonComponent, (prevProps, nextProps) => prevProps.timelineId === nextProps.timelineId && prevProps.usersViewing === nextProps.usersViewing ); -FlyoutHeaderWithCloseButton.displayName = 'FlyoutHeaderWithCloseButton'; - -const FlyoutPaneComponent = React.memo( - ({ - applyDeltaToWidth, - children, - flyoutHeight, - headerHeight, - onClose, - timelineId, - usersViewing, - width, - }) => { - const renderFlyout = useCallback(() => <>, []); - - const onResize: OnResize = useCallback( - ({ delta, id }) => { - const bodyClientWidthPixels = document.body.clientWidth; - +const FlyoutPaneComponent: React.FC = ({ + applyDeltaToWidth, + children, + flyoutHeight, + headerHeight, + onClose, + timelineId, + usersViewing, + width, +}) => { + const [lastDelta, setLastDelta] = useState(0); + const onResizeStop: ResizeCallback = useCallback( + (e, direction, ref, delta) => { + const bodyClientWidthPixels = document.body.clientWidth; + + if (delta.width) { applyDeltaToWidth({ bodyClientWidthPixels, - delta, - id, + delta: -(delta.width - lastDelta), + id: timelineId, maxWidthPercent, minWidthPixels, }); - }, - [applyDeltaToWidth, maxWidthPercent, minWidthPixels] - ); - return ( - - - setLastDelta(0), [setLastDelta]); + const throttledResize = throttle(100, onResizeStop); + + return ( + + + - } - id={timelineId} - onResize={onResize} - render={renderFlyout} - /> + ), + }} + onResizeStart={resetLastDelta} + onResize={throttledResize} + > ( {children} - - - ); - } -); - -FlyoutPaneComponent.displayName = 'FlyoutPaneComponent'; + + + + ); +}; export const Pane = connect(null, { applyDeltaToWidth: timelineActions.applyDeltaToWidth, -})(FlyoutPaneComponent); +})(React.memo(FlyoutPaneComponent)); Pane.displayName = 'Pane'; diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/styled_handles.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/timeline_resize_handle.tsx similarity index 70% rename from x-pack/legacy/plugins/siem/public/components/resize_handle/styled_handles.tsx rename to x-pack/legacy/plugins/siem/public/components/flyout/pane/timeline_resize_handle.tsx index 4f641c5d2042ef..3ee29c2eaaa169 100644 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/styled_handles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/timeline_resize_handle.tsx @@ -8,18 +8,13 @@ import styled from 'styled-components'; export const TIMELINE_RESIZE_HANDLE_WIDTH = 2; // px -export const CommonResizeHandle = styled.div` +export const TimelineResizeHandle = styled.div<{ height: number }>` cursor: col-resize; height: 100%; min-height: 20px; width: 0; -`; -CommonResizeHandle.displayName = 'CommonResizeHandle'; - -export const TimelineResizeHandle = styled(CommonResizeHandle)<{ height: number }>` border: ${TIMELINE_RESIZE_HANDLE_WIDTH}px solid ${props => props.theme.eui.euiColorLightShade}; z-index: 2; height: ${({ height }) => `${height}px`}; position: absolute; `; -TimelineResizeHandle.displayName = 'TimelineResizeHandle'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap index d23b1c61f7aeeb..ae30325f2a93b7 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`formatted_bytes PreferenceFormattedBytes rendering renders correctly against snapshot 1`] = ` +exports[`PreferenceFormattedBytes renders correctly against snapshot 1`] = ` 2.7MB diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx index 8c27a55d3a6b0e..914d233bccc1fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx @@ -5,10 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; -import { mockFrameworks, getMockKibanaUiSetting } from '../../mock'; import { useUiSetting$ } from '../../lib/kibana'; import { PreferenceFormattedBytesComponent } from '.'; @@ -16,50 +14,42 @@ import { PreferenceFormattedBytesComponent } from '.'; jest.mock('../../lib/kibana'); const mockUseUiSetting$ = useUiSetting$ as jest.Mock; -describe('formatted_bytes', () => { - describe('PreferenceFormattedBytes', () => { - describe('rendering', () => { - beforeEach(() => { - mockUseUiSetting$.mockClear(); - }); +const DEFAULT_BYTES_FORMAT_VALUE = '0,0.[0]b'; // kibana's default for this setting +const bytes = '2806422'; - const bytes = '2806422'; +describe('PreferenceFormattedBytes', () => { + test('renders correctly against snapshot', () => { + mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); + const wrapper = shallow(); - test('renders correctly against snapshot', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); - }); + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders bytes to Numeral formatting when no format setting exists', () => { + mockUseUiSetting$.mockImplementation(() => [null]); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2,806,422'); + }); - test('it renders bytes to hardcoded format when no configuration exists', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - expect(wrapper.text()).toEqual('2.7MB'); - }); + test('it renders bytes according to the default format', () => { + mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); + const wrapper = mount(); - test('it renders bytes according to the default format', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = mount(); - expect(wrapper.text()).toEqual('2.7MB'); - }); + expect(wrapper.text()).toEqual('2.7MB'); + }); + + test('it renders bytes supplied as a number according to the default format', () => { + mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2.7MB'); + }); - test('it renders bytes supplied as a number according to the default format', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = mount(); - expect(wrapper.text()).toEqual('2.7MB'); - }); + test('it renders bytes according to new format', () => { + mockUseUiSetting$.mockImplementation(() => ['0b']); + const wrapper = mount(); - test('it renders bytes according to new format', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.bytes_short)); - const wrapper = mount(); - expect(wrapper.text()).toEqual('3MB'); - }); - }); + expect(wrapper.text()).toEqual('3MB'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx index 003ce0879b7b51..98a1acf471629e 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx @@ -4,19 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import numeral from '@elastic/numeral'; import { DEFAULT_BYTES_FORMAT } from '../../../common/constants'; import { useUiSetting$ } from '../../lib/kibana'; -export const PreferenceFormattedBytesComponent = ({ value }: { value: string | number }) => { +type Bytes = string | number; + +export const formatBytes = (value: Bytes, format: string) => { + return numeral(value).format(format); +}; + +export const useFormatBytes = () => { const [bytesFormat] = useUiSetting$(DEFAULT_BYTES_FORMAT); - return ( - <>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[0]b')} - ); + + return (value: Bytes) => formatBytes(value, bytesFormat); }; +export const PreferenceFormattedBytesComponent = ({ value }: { value: Bytes }) => ( + <>{useFormatBytes()(value)} +); + PreferenceFormattedBytesComponent.displayName = 'PreferenceFormattedBytesComponent'; export const PreferenceFormattedBytes = React.memo(PreferenceFormattedBytesComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap index d196a23bff5bf7..9e851ddcd7d0f7 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`formatted_date PreferenceFormattedDate rendering renders correctly against snapshot 1`] = ` +exports[`formatted_date PreferenceFormattedDate renders correctly against snapshot 1`] = ` - 2019-02-25T22:27:05.000Z + 2019-02-25T22:27:05Z `; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx index dad1d5feb5c6e8..0d8222ce85e1be 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx @@ -5,175 +5,165 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; -import { useUiSetting$ } from '../../lib/kibana'; +import { useDateFormat, useTimeZone } from '../../hooks'; -import { mockFrameworks, TestProviders, MockFrameworks, getMockKibanaUiSetting } from '../../mock'; +import { TestProviders } from '../../mock'; import { getEmptyString, getEmptyValue } from '../empty_value'; import { PreferenceFormattedDate, FormattedDate, FormattedRelativePreferenceDate } from '.'; -jest.mock('../../lib/kibana'); -const mockUseUiSetting$ = useUiSetting$ as jest.Mock; +jest.mock('../../hooks'); +const mockUseDateFormat = useDateFormat as jest.Mock; +const mockUseTimeZone = useTimeZone as jest.Mock; + +const isoDateString = '2019-02-25T22:27:05.000Z'; describe('formatted_date', () => { + let isoDate: Date; + + beforeEach(() => { + isoDate = new Date(isoDateString); + mockUseDateFormat.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS'); + mockUseTimeZone.mockImplementation(() => 'UTC'); + }); + describe('PreferenceFormattedDate', () => { - describe('rendering', () => { - const isoDateString = '2019-02-25T22:27:05.000Z'; - const isoDate = new Date(isoDateString); - const configFormattedDateString = (dateString: string, config: MockFrameworks): string => - moment - .tz( - dateString, - config.dateFormatTz! === 'Browser' ? config.timezone! : config.dateFormatTz! - ) - .format(config.dateFormat); - - test('renders correctly against snapshot', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - test('it renders the UTC ISO8601 date string supplied when no configuration exists', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - expect(wrapper.text()).toEqual(isoDateString); - }); - - test('it renders the UTC ISO8601 date supplied when the default configuration exists', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - - const wrapper = mount(); - expect(wrapper.text()).toEqual( - configFormattedDateString(isoDateString, mockFrameworks.default_UTC) - ); - }); - - test('it renders the correct tz when the default browser configuration exists', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = mount(); - expect(wrapper.text()).toEqual( - configFormattedDateString(isoDateString, mockFrameworks.default_browser) - ); - }); - - test('it renders the correct tz when a non-UTC configuration exists', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_MT)); - const wrapper = mount(); - expect(wrapper.text()).toEqual( - configFormattedDateString(isoDateString, mockFrameworks.default_MT) - ); - }); + test('renders correctly against snapshot', () => { + mockUseDateFormat.mockImplementation(() => ''); + const wrapper = mount(); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the date with the default configuration', () => { + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb 25, 2019 @ 22:27:05.000'); + }); + + test('it renders a UTC ISO8601 date string supplied when no date format configuration exists', () => { + mockUseDateFormat.mockImplementation(() => ''); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2019-02-25T22:27:05Z'); + }); + + test('it renders the correct timezone when a non-UTC configuration exists', () => { + mockUseTimeZone.mockImplementation(() => 'America/Denver'); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb 25, 2019 @ 15:27:05.000'); + }); + + test('it renders the date with a user-defined format', () => { + mockUseDateFormat.mockImplementation(() => 'MMM-DD-YYYY'); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb-25-2019'); }); }); describe('FormattedDate', () => { - describe('rendering', () => { - test('it renders against a numeric epoch', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount(); - expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); - }); - - test('it renders against a string epoch', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount(); - expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); - }); - - test('it renders against a ISO string', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - ); - expect(wrapper.text()).toEqual('May 28, 2019 @ 22:04:49.957'); - }); - - test('it renders against an empty string as an empty string placeholder', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual('(Empty String)'); - }); - - test('it renders against an null as a EMPTY_VALUE', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual(getEmptyValue()); - }); - - test('it renders against an undefined as a EMPTY_VALUE', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual(getEmptyValue()); - }); - - test('it renders against an invalid date time as just the string its self', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual('Rebecca Evan Braden'); - }); + test('it renders against a numeric epoch', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); + }); + + test('it renders against a string epoch', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); + }); + + test('it renders against a ISO string', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('May 28, 2019 @ 22:04:49.957'); + }); + + test('it renders against an empty string as an empty string placeholder', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyString()); + }); + + test('it renders against an null as a EMPTY_VALUE', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyValue()); + }); + + test('it renders against an undefined as a EMPTY_VALUE', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyValue()); + }); + + test('it renders against an invalid date time as just the string its self', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual('Rebecca Evan Braden'); }); }); describe('FormattedRelativePreferenceDate', () => { - describe('rendering', () => { - test('renders time over an hour correctly against snapshot', () => { - const isoDateString = '2019-02-25T22:27:05.000Z'; - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true); - }); - test('renders time under an hour correctly against snapshot', () => { - const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString(); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true); - }); - test('renders empty string value correctly', () => { - const wrapper = mount( - - - - ); - expect(wrapper.text()).toBe(getEmptyString()); - }); - - test('renders undefined value correctly', () => { - const wrapper = mount( - - - - ); - expect(wrapper.text()).toBe(getEmptyValue()); - }); - - test('renders null value correctly', () => { - const wrapper = mount( - - - - ); - expect(wrapper.text()).toBe(getEmptyValue()); - }); + test('renders time over an hour correctly against snapshot', () => { + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true); + }); + + test('renders time under an hour correctly against snapshot', () => { + const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString(); + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true); + }); + + test('renders empty string value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyString()); + }); + + test('renders undefined value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('renders null value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyValue()); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx index 19e8ec3f95d269..4e5903c02abf7a 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx @@ -5,32 +5,19 @@ */ import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { FormattedRelative } from '@kbn/i18n/react'; -import { useUiSetting$ } from '../../lib/kibana'; - -import { - DEFAULT_DATE_FORMAT, - DEFAULT_DATE_FORMAT_TZ, - DEFAULT_TIMEZONE_BROWSER, -} from '../../../common/constants'; +import { useDateFormat, useTimeZone } from '../../hooks'; import { getOrEmptyTagFromValue } from '../empty_value'; import { LocalizedDateTooltip } from '../localized_date_tooltip'; import { getMaybeDate } from './maybe_date'; export const PreferenceFormattedDate = React.memo<{ value: Date }>(({ value }) => { - const [dateFormat] = useUiSetting$(DEFAULT_DATE_FORMAT); - const [dateFormatTz] = useUiSetting$(DEFAULT_DATE_FORMAT_TZ); - const [timezone] = useUiSetting$(DEFAULT_TIMEZONE_BROWSER); + const dateFormat = useDateFormat(); + const timeZone = useTimeZone(); - return ( - <> - {dateFormat && dateFormatTz && timezone - ? moment.tz(value, dateFormatTz === 'Browser' ? timezone : dateFormatTz).format(dateFormat) - : moment.utc(value).toISOString()} - - ); + return <>{moment.tz(value, timeZone).format(dateFormat)}; }); PreferenceFormattedDate.displayName = 'PreferenceFormattedDate'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx index 8afbafe57af4ab..fa8d87cf4e82d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { getFormattedDurationString } from './helpers'; import { FormattedDurationTooltip } from './tooltip'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx index 1372b3ef10920b..6c11a7aad7ad21 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx @@ -5,7 +5,7 @@ */ import { EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx index 8dcb558122d015..48d34451404bea 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx @@ -5,7 +5,7 @@ */ import { isArray, isEmpty, isString, uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; import { escapeDataProviderId } from '../drag_and_drop/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index a45bed87829bfb..098de39bbfef53 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import '../../mock/match_media'; @@ -23,6 +22,6 @@ describe('HeaderGlobal', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx index 633ff90524de67..83a70fd90d82b5 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -29,7 +28,7 @@ describe('HeaderPage', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the title', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx index fbd8642c01facd..2bc80be20e42da 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -16,7 +15,7 @@ describe('HeaderSection', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the title', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx index 3a2e516fffb7e7..26c5f499717e9c 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { diff --git a/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx index 2dda3eca563fea..cac372a27180e6 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock/test_providers'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -20,7 +19,7 @@ describe('Port', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the the ip address', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/ip/index.tsx index 8c327989963b42..49237c3bb1bb96 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ip/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { FormattedFieldValue } from '../timeline/body/renderers/formatted_field'; diff --git a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx index 3842b7be678767..c4ea6ff63a0a77 100644 --- a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx index 950ab252ad0bd0..955a57576dc8eb 100644 --- a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx index dcecc636d9f0fd..69a795d0c8db78 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { getEmptyValue } from '../empty_value'; import { LastEventIndexKey } from '../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx index 87761a51a431f4..59f2acba4121bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -19,7 +18,7 @@ describe('LinkIcon', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders an action button when onClick is provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx index 21580a0ac86640..dd5c3bf8bb5d59 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { encodeIpv6 } from '../../lib/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.tsx index 9d8d01d2bb49aa..f63d13fcda7f03 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.tsx @@ -5,7 +5,7 @@ */ import { EuiLink } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { encodeIpv6 } from '../../lib/helpers'; import { getHostDetailsUrl, getIPDetailsUrl } from '../link_to'; diff --git a/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx index 1c97e766345aae..48dd91922583fa 100644 --- a/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { Loader } from './index'; @@ -17,6 +16,6 @@ describe('rendering', () => { {'Loading'} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/loading/index.tsx b/x-pack/legacy/plugins/siem/public/components/loading/index.tsx index cd437911ab5897..8c39a3d6ffcbe1 100644 --- a/x-pack/legacy/plugins/siem/public/components/loading/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/loading/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPanel, EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; const SpinnerFlexItem = styled(EuiFlexItem)` diff --git a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx index 5feb70edffb9a8..c25f0d71833c02 100644 --- a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { LocalizedDateTooltip } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx index 82520ce2745c0b..ed0b1b16892188 100644 --- a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import moment from 'moment'; -import * as React from 'react'; +import React from 'react'; export const LocalizedDateTooltip = React.memo<{ children: React.ReactNode; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx index 56c215218ad5ed..de662c162fc0a1 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Markdown } from '.'; @@ -98,7 +97,7 @@ describe('Markdown', () => { test('it renders the expected table content', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); @@ -152,7 +151,7 @@ describe('Markdown', () => { test('it renders the expected content containing a link', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx index 0727b6794f48ce..30695c9d0c7e2a 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx @@ -7,7 +7,7 @@ /* eslint-disable react/display-name */ import { EuiLink, EuiTableRow, EuiTableRowCell, EuiText, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx index 80ccd07c30249b..59aae5abce5c4e 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MarkdownHintComponent } from './markdown_hint'; @@ -89,7 +88,7 @@ describe('MarkdownHintComponent ', () => { test('it renders the expected hints', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx index 5ecd1d4c9d2ad3..199059670e4bd2 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx @@ -5,7 +5,7 @@ */ import { EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx index fd72fcd7322b04..78a4c967ee0bba 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx @@ -7,7 +7,7 @@ /* eslint-disable react/display-name */ import { shallow } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { MatrixHistogram } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts index 1eb5e96b868578..a7ef71f7a6a0d3 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts @@ -6,7 +6,7 @@ import { ScaleType, niceTimeFormatter, Position } from '@elastic/charts'; import { get, groupBy, map, toPairs } from 'lodash/fp'; -import numeral from '@elastic/numeral'; + import { UpdateDateRange, ChartSeriesData } from '../charts/common'; import { MatrixHistogramDataTypes, MatrixHistogramMappingTypes } from './types'; @@ -87,7 +87,3 @@ export const getCustomChartData = ( }, formattedChartData); else return formattedChartData; }; - -export const bytesFormatter = (value: number) => { - return numeral(value).format('0,0.[0]b'); -}; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx index 3966448f84df02..6ccc41546e5587 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { InfluencerInput, Anomalies, CriteriaFields } from '../types'; import { useAnomaliesTableData } from './use_anomalies_table_data'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts index bce99c943c7a5d..48277b0b6fa52c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts @@ -15,11 +15,8 @@ import { errorToToaster } from '../api/error_to_toaster'; import * as i18n from './translations'; import { useUiSetting$ } from '../../../lib/kibana'; -import { - DEFAULT_ANOMALY_SCORE, - DEFAULT_TIMEZONE_BROWSER, - DEFAULT_KBN_VERSION, -} from '../../../../common/constants'; +import { DEFAULT_ANOMALY_SCORE } from '../../../../common/constants'; +import { useTimeZone } from '../../../hooks'; interface Args { influencers?: InfluencerInput[]; @@ -67,9 +64,8 @@ export const useAnomaliesTableData = ({ const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [, dispatchToaster] = useStateToaster(); - const [timezone] = useUiSetting$(DEFAULT_TIMEZONE_BROWSER); + const timeZone = useTimeZone(); const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const siemJobIds = siemJobs.filter(job => job.isInstalled).map(job => job.id); @@ -95,11 +91,10 @@ export const useAnomaliesTableData = ({ earliestMs, latestMs, influencers: influencersInput, - dateFormatTz: timezone, + dateFormatTz: timeZone, maxRecords: 500, maxExamples: 10, }, - kbnVersion, abortCtrl.signal ); if (isSubscribed) { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts index e66d984a152941..10b2538d1e7852 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts @@ -21,20 +21,15 @@ export interface Body { maxExamples: number; } -export const anomaliesTableData = async ( - body: Body, - kbnVersion: string, - signal: AbortSignal -): Promise => { +export const anomaliesTableData = async (body: Body, signal: AbortSignal): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/results/anomalies_table_data`, { method: 'POST', credentials: 'same-origin', body: JSON.stringify(body), headers: { - 'kbn-system-api': 'true', 'content-Type': 'application/json', - 'kbn-xsrf': kbnVersion, - 'kbn-version': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts index c1654a1648f2b5..13339510284945 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts @@ -22,18 +22,14 @@ export interface Body { maxExamples: number; } -export const getMlCapabilities = async ( - kbnVersion: string, - signal: AbortSignal -): Promise => { +export const getMlCapabilities = async (signal: AbortSignal): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/ml_capabilities`, { method: 'GET', credentials: 'same-origin', headers: { - 'kbn-system-api': 'true', 'content-Type': 'application/json', - 'kbn-xsrf': kbnVersion, - 'kbn-version': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx index 562e3c15675a7f..c48a5590b49cfd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import toJson from 'enzyme-to-json'; import { shallow } from 'enzyme'; import { EntityDraggableComponent } from './entity_draggable'; import { TestProviders } from '../../mock/test_providers'; @@ -22,7 +21,7 @@ describe('entity_draggable', () => { entityValue="entity-value" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('renders with entity name with entity value as text', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx index 615e83d208dd63..d49c3008b696cf 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import toJson from 'enzyme-to-json'; import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow, mount } from 'enzyme'; @@ -20,7 +19,7 @@ describe('create_influencers', () => { test('renders correctly against snapshot', () => { const wrapper = shallow({createInfluencers(anomalies.anomalies[0].influencers)}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns an empty string when influencers is undefined', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx index b8d6908df464e7..cae05e26b115b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx @@ -11,8 +11,6 @@ import { getMlCapabilities } from '../api/get_ml_capabilities'; import { emptyMlCapabilities } from '../empty_ml_capabilities'; import { errorToToaster } from '../api/error_to_toaster'; import { useStateToaster } from '../../toasters'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import * as i18n from './translations'; @@ -36,7 +34,6 @@ export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ c emptyMlCapabilitiesProvider ); const [, dispatchToaster] = useStateToaster(); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; @@ -44,7 +41,7 @@ export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ c async function fetchMlCapabilities() { try { - const mlCapabilities = await getMlCapabilities(kbnVersion, abortCtrl.signal); + const mlCapabilities = await getMlCapabilities(abortCtrl.signal); if (isSubscribed) { setCapabilities({ ...mlCapabilities, capabilitiesFetched: true }); } diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx index cf24d6c02a1381..9ff0081f4359f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { AnomalyScoreComponent } from './anomaly_score'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; @@ -36,7 +35,7 @@ describe('anomaly_scores', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should not show a popover on initial render', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx index 759e84e36f4acc..3041134f669eeb 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { AnomalyScoresComponent, createJobKey } from './anomaly_scores'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; @@ -36,7 +35,7 @@ describe('anomaly_scores', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('renders spinner when isLoading is true is passed', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx index f00fb62d74ac3e..7c8900bf77d95b 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx @@ -5,8 +5,7 @@ */ import { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockAnomalies } from '../mock'; import { createDescriptionList } from './create_description_list'; import { EuiDescriptionList } from '@elastic/eui'; @@ -35,7 +34,7 @@ describe('create_description_list', () => { )} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it calls the narrow date range function on click', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx index 0d389ae14a8255..f7759bb74c3abc 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import toJson from 'enzyme-to-json'; import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow } from 'enzyme'; @@ -22,13 +21,13 @@ describe('draggable_score', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('renders correctly against snapshot when the index is not included', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx index 8e7bedd8f872a0..a04b8f4b996536 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx @@ -25,12 +25,10 @@ import { throwIfNotOk } from '../../hooks/api/api'; * Checks the ML Recognizer API to see if a given indexPattern has any compatible modules * * @param indexPatternName ES index pattern to check for compatible modules - * @param headers optional headers to add * @param signal to cancel request */ export const checkRecognizer = async ({ indexPatternName, - kbnVersion, signal, }: CheckRecognizerProps): Promise => { const response = await fetch( @@ -39,10 +37,9 @@ export const checkRecognizer = async ({ method: 'GET', credentials: 'same-origin', headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, } @@ -55,22 +52,16 @@ export const checkRecognizer = async ({ * Returns ML Module for given moduleId. Returns all modules if no moduleId specified * * @param moduleId id of the module to retrieve - * @param headers optional headers to add optional headers to add * @param signal to cancel request */ -export const getModules = async ({ - moduleId = '', - kbnVersion, - signal, -}: GetModulesProps): Promise => { +export const getModules = async ({ moduleId = '', signal }: GetModulesProps): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/get_module/${moduleId}`, { method: 'GET', credentials: 'same-origin', headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, }); @@ -93,7 +84,6 @@ export const setupMlJob = async ({ indexPatternName = 'auditbeat-*', jobIdErrorFilter = [], groups = ['siem'], - kbnVersion, prefix = '', }: MlSetupArgs): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/setup/${configTemplate}`, { @@ -107,10 +97,9 @@ export const setupMlJob = async ({ useDedicatedIndex: true, }), headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, }); await throwIfNotOk(response); @@ -124,16 +113,13 @@ export const setupMlJob = async ({ * * @param datafeedIds * @param start - * @param headers optional headers to add */ export const startDatafeeds = async ({ datafeedIds, - kbnVersion, start = 0, }: { datafeedIds: string[]; start: number; - kbnVersion: string; }): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/force_start_datafeeds`, { method: 'POST', @@ -143,10 +129,9 @@ export const startDatafeeds = async ({ ...(start !== 0 && { start }), }), headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, }); await throwIfNotOk(response); @@ -163,10 +148,8 @@ export const startDatafeeds = async ({ */ export const stopDatafeeds = async ({ datafeedIds, - kbnVersion, }: { datafeedIds: string[]; - kbnVersion: string; }): Promise<[StopDatafeedResponse | ErrorResponse, CloseJobsResponse]> => { const stopDatafeedsResponse = await fetch(`${chrome.getBasePath()}/api/ml/jobs/stop_datafeeds`, { method: 'POST', @@ -175,9 +158,9 @@ export const stopDatafeeds = async ({ datafeedIds, }), headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, }); @@ -198,7 +181,7 @@ export const stopDatafeeds = async ({ headers: { 'content-type': 'application/json', 'kbn-system-api': 'true', - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, }); @@ -214,10 +197,7 @@ export const stopDatafeeds = async ({ * * @param signal to cancel request */ -export const getJobsSummary = async ( - signal: AbortSignal, - kbnVersion: string -): Promise => { +export const getJobsSummary = async (signal: AbortSignal): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/jobs_summary`, { method: 'POST', credentials: 'same-origin', @@ -225,8 +205,7 @@ export const getJobsSummary = async ( headers: { 'content-type': 'application/json', 'kbn-system-api': 'true', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx index f9d110d711d075..9df93d087e1668 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx @@ -13,7 +13,7 @@ import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_prov import { useStateToaster } from '../../toasters'; import { errorToToaster } from '../../ml/api/error_to_toaster'; import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_INDEX_KEY, DEFAULT_KBN_VERSION } from '../../../../common/constants'; +import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import * as i18n from './translations'; import { createSiemJobs } from './use_siem_jobs_helpers'; @@ -34,7 +34,6 @@ export const useSiemJobs = (refetchData: boolean): Return => { const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [siemDefaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -47,11 +46,10 @@ export const useSiemJobs = (refetchData: boolean): Return => { try { // Batch fetch all installed jobs, ML modules, and check which modules are compatible with siemDefaultIndex const [jobSummaryData, modulesData, compatibleModules] = await Promise.all([ - getJobsSummary(abortCtrl.signal, kbnVersion), - getModules({ signal: abortCtrl.signal, kbnVersion }), + getJobsSummary(abortCtrl.signal), + getModules({ signal: abortCtrl.signal }), checkRecognizer({ indexPatternName: siemDefaultIndex, - kbnVersion, signal: abortCtrl.signal, }), ]); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx index 542529c628b727..a14ff789f10787 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { GroupsFilterPopoverComponent } from './groups_filter_popover'; import { mockSiemJobs } from '../../__mocks__/api'; import { SiemJob } from '../../types'; @@ -23,7 +22,7 @@ describe('GroupsFilterPopover', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('when a filter is clicked, it becomes checked ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx index 0711cc1c879660..cbee7248021560 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { JobsTableFiltersComponent } from './jobs_table_filters'; import { SiemJob } from '../../types'; import { cloneDeep } from 'lodash/fp'; @@ -23,7 +22,7 @@ describe('JobsTableFilters', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('when you click Elastic Jobs filter, state is updated and it is selected', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx index 91e5510f4938d4..1186573e3e2090 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx @@ -5,8 +5,7 @@ */ import { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { isChecked, isFailure, isJobLoading, JobSwitchComponent } from './job_switch'; import { cloneDeep } from 'lodash/fp'; @@ -29,7 +28,7 @@ describe('JobSwitch', () => { onJobStateChange={onJobStateChangeMock} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should call onJobStateChange when the switch is clicked to be true/open', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx index 691d43a8b18b36..fa524d8ff3dbc6 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx @@ -5,8 +5,7 @@ */ import { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { JobsTableComponent } from './jobs_table'; import { mockSiemJobs } from '../__mocks__/api'; import { cloneDeep } from 'lodash/fp'; @@ -28,7 +27,7 @@ describe('JobsTableComponent', () => { onJobStateChange={onJobStateChangeMock} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render the hyperlink which points specifically to the job id', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx index 2e2445fe933bbc..bf1802f42084e3 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx @@ -5,13 +5,12 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ShowingCountComponent } from './showing_count'; describe('ShowingCount', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx index 987c63be3f7be1..bd7d696757ca6d 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { MlPopover } from './ml_popover'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx index c34ed51d229942..307be06424ee3f 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx @@ -10,8 +10,7 @@ import moment from 'moment'; import React, { useContext, useReducer, useState } from 'react'; import styled from 'styled-components'; -import { DEFAULT_KBN_VERSION } from '../../../common/constants'; -import { useKibana, useUiSetting$ } from '../../lib/kibana'; +import { useKibana } from '../../lib/kibana'; import { METRIC_TYPE, TELEMETRY_EVENT, trackUiAction as track } from '../../lib/track_usage'; import { errorToToaster } from '../ml/api/error_to_toaster'; import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions'; @@ -97,7 +96,6 @@ export const MlPopover = React.memo(() => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [filterProperties, setFilterProperties] = useState(defaultFilterProps); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [isLoadingSiemJobs, siemJobs] = useSiemJobs(refreshToggle); const [, dispatchToaster] = useStateToaster(); const capabilities = useContext(MlCapabilitiesContext); @@ -114,7 +112,6 @@ export const MlPopover = React.memo(() => { indexPatternName: job.defaultIndexPattern, jobIdErrorFilter: [job.id], groups: job.groups, - kbnVersion, }); } catch (error) { errorToToaster({ title: i18n.CREATE_JOB_FAILURE, error, dispatchToaster }); @@ -132,14 +129,14 @@ export const MlPopover = React.memo(() => { if (enable) { const startTime = Math.max(latestTimestampMs, maxStartTime); try { - await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], kbnVersion, start: startTime }); + await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], start: startTime }); } catch (error) { track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); errorToToaster({ title: i18n.START_JOB_FAILURE, error, dispatchToaster }); } } else { try { - await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`], kbnVersion }); + await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`] }); } catch (error) { track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); errorToToaster({ title: i18n.STOP_JOB_FAILURE, error, dispatchToaster }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx index d409f5de200a47..e611a5234da5ed 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx @@ -5,13 +5,12 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { PopoverDescriptionComponent } from './popover_description'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts index f8794c19639618..964ae8c8242d4b 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts @@ -14,7 +14,6 @@ export interface Group { export interface CheckRecognizerProps { indexPatternName: string[]; - kbnVersion: string; signal: AbortSignal; } @@ -30,7 +29,6 @@ export interface RecognizerModule { export interface GetModulesProps { moduleId?: string; - kbnVersion: string; signal: AbortSignal; } @@ -97,7 +95,6 @@ export interface MlSetupArgs { jobIdErrorFilter: string[]; groups: string[]; prefix?: string; - kbnVersion: string; } /** diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx index c522b7750c4149..2ba08073b25b94 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx @@ -5,13 +5,12 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { UpgradeContentsComponent } from './upgrade_contents'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index d7061ba4efd9cb..cae209a76fc1ce 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { CONSTANTS } from '../url_state/constants'; import { SiemNavigationComponent } from './'; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx index 840e2bf3f42dcd..b9563b60f301bb 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { navTabs } from '../../../pages/home/home_navigations'; import { SiemPageName } from '../../../pages/home/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx index 6b29068f3cd2de..f0dc67a5ff4c3b 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { CertificateFingerprint, diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx index 22531983b23992..ecf162ebf2739a 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import toJson from 'enzyme-to-json'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { shallow } from 'enzyme'; import { asArrayIfExists } from '../../lib/helpers'; @@ -123,7 +122,7 @@ describe('Netflow', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(getNetflowInstance()); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a destination label', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx index 26e6986c386be9..3d6c2a7b767cb0 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Fingerprints } from './fingerprints'; import { NetflowColumns } from './netflow_columns'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx index f006ec0f003c18..09fa5d9fe1596a 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DefaultDraggable } from '../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx index 0dc4a2271e207f..f8a0256ff4d435 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { SourceDestination } from '../../source_destination'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx index adac7dede1f851..ab71dc301156f8 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx index fc76780ef80c79..ca6abc90d317b8 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { AddNote } from '.'; @@ -24,7 +23,7 @@ describe('AddNote', () => { updateNote={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the Cancel button when onCancelAddNote is provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx index 3ab556a4e5dc4c..24db5c5ec8125e 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../translations'; @@ -19,7 +18,7 @@ describe('NewNote', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a tab labeled "Note"', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx index 64cbdf1c678c55..5a3439d53dd891 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel, EuiTabbedContent, EuiTextArea } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Markdown } from '../../markdown'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx b/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx index fbcb3c13be67ac..32e10ac3eb77d1 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx @@ -6,7 +6,7 @@ /* eslint-disable react/display-name */ -import * as React from 'react'; +import React from 'react'; import { EuiTableDataType } from '@elastic/eui'; import { NoteCard } from './note_card'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx index 65e46f8d84c9f0..c933055186e078 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx @@ -6,7 +6,7 @@ import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import moment from 'moment'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Note } from '../../lib/note'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx index 2c16f85c780764..a927627353f691 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx index 88e59ddd1419b5..e02ebc2a25fd0c 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { NoteCardBody } from './note_card_body'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx index b6cd23496b1902..46e1bab37495a1 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; @@ -24,7 +23,7 @@ describe('NoteCardBody', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the text of the note in an h1', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx index 94ea0a6ee3129d..11761c8fd39b04 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { WithCopyToClipboard } from '../../../lib/clipboard/with_copy_to_clipboard'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx index 0511ad264647a7..3525a88fc2ddd4 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx @@ -5,7 +5,7 @@ */ import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx index a227b5af38e85c..e6aa0542df4b35 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx @@ -5,7 +5,7 @@ */ import { EuiAvatar, EuiPanel } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx index 9c41d9d52a755e..5d99375c38217f 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx @@ -5,7 +5,7 @@ */ import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { NoteCreated } from './note_created'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx index c94f4021e1a4aa..cdd0406c714509 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx @@ -5,7 +5,7 @@ */ import { FormattedRelative } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { LocalizedDateTooltip } from '../../localized_date_tooltip'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx index dc52822eaff778..f70e841d1eefd3 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx index 917ec3f1bf0b86..e061141bf43e78 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx @@ -5,7 +5,7 @@ */ import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { DeleteTimelineModal } from './delete_timeline_modal'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx index 1163fbba1572a7..82fe0d1d162a4b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx @@ -6,7 +6,7 @@ import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx index 561eac000bbf76..a3c5371435e52c 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx @@ -6,7 +6,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { DeleteTimelineModalButton } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts index 91480f20d8b004..41e13408c1e01d 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts @@ -181,6 +181,7 @@ export interface QueryTimelineById { apolloClient: ApolloClient | ApolloClient<{}> | undefined; duplicate: boolean; timelineId: string; + onOpenTimeline?: (timeline: TimelineModel) => void; openTimeline?: boolean; updateIsLoading: ActionCreator<{ id: string; isLoading: boolean }>; updateTimeline: DispatchUpdateTimeline; @@ -190,6 +191,7 @@ export const queryTimelineById = ({ apolloClient, duplicate = false, timelineId, + onOpenTimeline, openTimeline = true, updateIsLoading, updateTimeline, @@ -209,7 +211,9 @@ export const queryTimelineById = ({ ); const { timeline, notes } = formatTimelineResultToModel(timelineToOpen, duplicate); - if (updateTimeline) { + if (onOpenTimeline != null) { + onOpenTimeline(timeline); + } else if (updateTimeline) { updateTimeline({ duplicate, from: getOr(getDefaultFromValue(), 'dateRange.start', timeline), diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx index c9f52d9d204aed..520e2094fb3364 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; import { MockedProvider } from 'react-apollo/test-utils'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { wait } from '../../lib/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx index c22c5fdbcfbc59..a97cfefaf0393f 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx @@ -12,18 +12,20 @@ import { Dispatch } from 'redux'; import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers'; import { deleteTimelineMutation } from '../../containers/timeline/delete/persist.gql_query'; import { AllTimelinesVariables, AllTimelinesQuery } from '../../containers/timeline/all'; - import { allTimelinesQuery } from '../../containers/timeline/all/index.gql_query'; import { DeleteTimelineMutation, SortFieldTimeline, Direction } from '../../graphql/types'; import { State, timelineSelectors } from '../../store'; +import { timelineDefaults, TimelineModel } from '../../store/timeline/model'; import { createTimeline as dispatchCreateNewTimeline, updateIsLoading as dispatchUpdateIsLoading, } from '../../store/timeline/actions'; +import { ColumnHeader } from '../timeline/body/column_headers/column_header'; import { OpenTimeline } from './open_timeline'; import { OPEN_TIMELINE_CLASS_NAME, queryTimelineById, dispatchUpdateTimeline } from './helpers'; import { OpenTimelineModalBody } from './open_timeline_modal/open_timeline_modal_body'; import { + ActionTimelineToShow, DeleteTimelines, EuiSearchBarQuery, OnDeleteSelected, @@ -41,14 +43,14 @@ import { OpenTimelineReduxProps, } from './types'; import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants'; -import { ColumnHeader } from '../timeline/body/column_headers/column_header'; -import { timelineDefaults } from '../../store/timeline/model'; interface OwnProps { apolloClient: ApolloClient; /** Displays open timeline in modal */ isModal: boolean; closeModalTimeline?: () => void; + hideActions?: ActionTimelineToShow[]; + onOpenTimeline?: (timeline: TimelineModel) => void; } export type OpenTimelineOwnProps = OwnProps & @@ -69,15 +71,17 @@ export const getSelectedTimelineIds = (selectedItems: OpenTimelineResult[]): str /** Manages the state (e.g table selection) of the (pure) `OpenTimeline` component */ export const StatefulOpenTimelineComponent = React.memo( ({ + apolloClient, + closeModalTimeline, + createNewTimeline, defaultPageSize, + hideActions = [], isModal = false, + onOpenTimeline, + timeline, title, - apolloClient, - closeModalTimeline, updateTimeline, updateIsLoading, - timeline, - createNewTimeline, }) => { /** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */ const [itemIdToExpandedNotesRowMap, setItemIdToExpandedNotesRowMap] = useState< @@ -212,6 +216,7 @@ export const StatefulOpenTimelineComponent = React.memo( queryTimelineById({ apolloClient, duplicate, + onOpenTimeline, timelineId, updateIsLoading, updateTimeline, @@ -286,6 +291,7 @@ export const StatefulOpenTimelineComponent = React.memo( data-test-subj={'open-timeline-modal'} deleteTimelines={onDeleteOneTimeline} defaultPageSize={defaultPageSize} + hideActions={hideActions} isLoading={loading} itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap} onAddTimelinesToFavorites={undefined} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx index 70bf7d2cbeb524..463111bd9735f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx @@ -8,7 +8,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import moment from 'moment'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { mockTimelineResults } from '../../../mock/timeline_results'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx index 2c35d5d8254cd4..e0507c2370831d 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx @@ -5,7 +5,7 @@ */ import { uniqBy } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { NotePreview } from './note_preview'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx index 8092172a29cefa..7cefaf08d76cb2 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { getEmptyValue } from '../../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx index 126a14f1f8e32f..bb4a032734b5b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx @@ -6,7 +6,7 @@ import { EuiAvatar, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle, EuiToolTip } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { getEmptyValue, defaultToEmptyTag } from '../../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx index dbc7199aac7253..a1ca7812bba340 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../pages/timelines/timelines_page'; @@ -143,7 +143,7 @@ describe('OpenTimeline', () => { ).toBe(true); }); - test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => { + test('it shows the delete action columns when onDeleteSelected and deleteTimelines are specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(true); + expect(props.actionTimelineToShow).toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => { + test('it does NOT show the delete action columns when is onDeleteSelected undefined and deleteTimelines is specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => { + test('it does NOT show the delete action columns when is onDeleteSelected provided and deleteTimelines is undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => { + test('it does NOT show the delete action when both onDeleteSelected and deleteTimelines are undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx index 3b4057c69a696c..8aab02b495392f 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { OPEN_TIMELINE_CLASS_NAME } from './helpers'; import { OpenTimelineProps } from './types'; @@ -57,6 +57,11 @@ export const OpenTimeline = React.memo( /> ( pageIndex={pageIndex} pageSize={pageSize} searchResults={searchResults} - showExtendedColumnsAndActions={onDeleteSelected != null && deleteTimelines != null} + showExtendedColumns={true} sortDirection={sortDirection} sortField={sortField} totalSearchResultsCount={totalSearchResultsCount} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx index e3dc6d974b5e04..ca8fa50c572fe3 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx index cd89eb8aad6f43..c530929a3c96ee 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx @@ -7,39 +7,49 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import React from 'react'; +import { TimelineModel } from '../../../store/timeline/model'; import { useApolloClient } from '../../../utils/apollo_context'; + import * as i18n from '../translations'; +import { ActionTimelineToShow } from '../types'; import { StatefulOpenTimeline } from '..'; export interface OpenTimelineModalProps { onClose: () => void; + hideActions?: ActionTimelineToShow[]; + modalTitle?: string; + onOpen?: (timeline: TimelineModel) => void; } const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px -export const OpenTimelineModal = React.memo(({ onClose }) => { - const apolloClient = useApolloClient(); - - if (!apolloClient) return null; - - return ( - - - - - - ); -}); +export const OpenTimelineModal = React.memo( + ({ hideActions = [], modalTitle, onClose, onOpen }) => { + const apolloClient = useApolloClient(); + + if (!apolloClient) return null; + + return ( + + + + + + ); + } +); OpenTimelineModal.displayName = 'OpenTimelineModal'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index a5abb42c2e3b66..2c3adb138b7acf 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -143,7 +143,7 @@ describe('OpenTimelineModal', () => { ).toBe(true); }); - test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => { + test('it shows the delete action when onDeleteSelected and deleteTimelines are specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(true); + expect(props.actionTimelineToShow).toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => { + test('it does NOT show the delete when is onDeleteSelected undefined and deleteTimelines is specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => { + test('it does NOT show the delete action when is onDeleteSelected provided and deleteTimelines is undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => { + test('it does NOT show extended columns when both onDeleteSelected and deleteTimelines are undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx index b097d2e0d01ebf..dcd0b377705830 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx @@ -5,10 +5,10 @@ */ import { EuiModalBody, EuiModalHeader } from '@elastic/eui'; -import * as React from 'react'; +import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; -import { OpenTimelineProps } from '../types'; +import { OpenTimelineProps, ActionTimelineToShow } from '../types'; import { SearchRow } from '../search_row'; import { TimelinesTable } from '../timelines_table'; import { TitleRow } from '../title_row'; @@ -19,10 +19,11 @@ export const HeaderContainer = styled.div` HeaderContainer.displayName = 'HeaderContainer'; -export const OpenTimelineModalBody = React.memo( +export const OpenTimelineModalBody = memo( ({ deleteTimelines, defaultPageSize, + hideActions = [], isLoading, itemIdToExpandedNotesRowMap, onAddTimelinesToFavorites, @@ -43,51 +44,61 @@ export const OpenTimelineModalBody = React.memo( sortField, title, totalSearchResultsCount, - }) => ( - <> - - - + }) => { + const actionsToShow = useMemo(() => { + const actions: ActionTimelineToShow[] = + onDeleteSelected != null && deleteTimelines != null + ? ['delete', 'duplicate'] + : ['duplicate']; + return actions.filter(action => !hideActions.includes(action)); + }, [onDeleteSelected, deleteTimelines, hideActions]); + return ( + <> + + + + + + + - + - - - - - - - - ) + + + ); + } ); OpenTimelineModalBody.displayName = 'OpenTimelineModalBody'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx index 9a70fd476e89e9..66947a313f5e55 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx index c25bba7b6b041b..1a4708ed5af083 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx @@ -7,7 +7,7 @@ import { EuiFilterButtonProps } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { SearchRow } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx index fa16c8cfb70356..5765d31078bcf7 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx @@ -14,7 +14,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx index 749ba8672abead..eec11f571328f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -27,10 +27,11 @@ describe('#getActionsColumns', () => { mockResults = cloneDeep(mockTimelineResults); }); - test('it renders the delete timeline (trash icon) when showDeleteAction is true (because showExtendedColumnsAndActions is true)', () => { + test('it renders the delete timeline (trash icon) when actionTimelineToShow is including the action delete', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -53,10 +54,11 @@ describe('#getActionsColumns', () => { expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(true); }); - test('it does NOT render the delete timeline (trash icon) when showDeleteAction is false (because showExtendedColumnsAndActions is false)', () => { + test('it does NOT render the delete timeline (trash icon) when actionTimelineToShow is NOT including the action delete', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -79,10 +81,65 @@ describe('#getActionsColumns', () => { expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(false); }); + test('it renders the duplicate icon timeline when actionTimelineToShow is including the action duplicate', () => { + const wrapper = mountWithIntl( + + + + ); + + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBe(true); + }); + + test('it does NOT render the duplicate timeline when actionTimelineToShow is NOT including the action duplicate)', () => { + const wrapper = mountWithIntl( + + + + ); + + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBe(false); + }); + test('it does NOT render the delete timeline (trash icon) when deleteTimelines is not provided', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -111,6 +168,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -141,6 +199,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -174,6 +233,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx index 6934e69bd0f7fa..2b8bd3339cca24 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx @@ -7,24 +7,29 @@ /* eslint-disable react/display-name */ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; import { DeleteTimelineModalButton } from '../delete_timeline_modal'; import * as i18n from '../translations'; -import { DeleteTimelines, OnOpenTimeline, OpenTimelineResult } from '../types'; +import { + ActionTimelineToShow, + DeleteTimelines, + OnOpenTimeline, + OpenTimelineResult, +} from '../types'; /** * Returns the action columns (e.g. delete, open duplicate timeline) */ export const getActionsColumns = ({ + actionTimelineToShow, onOpenTimeline, deleteTimelines, - showDeleteAction, }: { + actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; onOpenTimeline: OnOpenTimeline; - showDeleteAction: boolean; }) => { const openAsDuplicateColumn = { align: 'center', @@ -67,7 +72,10 @@ export const getActionsColumns = ({ width: ACTION_COLUMN_WIDTH, }; - return showDeleteAction && deleteTimelines != null - ? [openAsDuplicateColumn, deleteTimelineColumn] - : [openAsDuplicateColumn]; + return [ + actionTimelineToShow.includes('duplicate') ? openAsDuplicateColumn : null, + actionTimelineToShow.includes('delete') && deleteTimelines != null + ? deleteTimelineColumn + : null, + ].filter(action => action != null); }; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx index fa08df1df4785b..0f2cda9d79f0b1 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx @@ -7,7 +7,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; @@ -37,6 +37,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -63,6 +64,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingNotes.length} @@ -89,6 +91,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={nullNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={nullNotes.length} @@ -115,6 +118,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptylNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptylNotes.length} @@ -143,6 +147,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -169,6 +174,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={nullSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={nullSavedObjectId.length} @@ -195,6 +201,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -231,6 +238,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -269,6 +277,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -311,6 +320,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -346,6 +356,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -377,6 +388,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -411,6 +423,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -442,6 +455,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingTitle} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingTitle.length} @@ -475,6 +489,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={withMissingSavedObjectIdAndTitle} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={withMissingSavedObjectIdAndTitle.length} @@ -508,6 +523,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={withJustWhitespaceTitle} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={withJustWhitespaceTitle.length} @@ -541,6 +557,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={withMissingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={withMissingSavedObjectId.length} @@ -571,6 +588,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -605,6 +623,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -637,6 +656,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -673,6 +693,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -704,6 +725,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -737,6 +759,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingDescription} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingDescription.length} @@ -771,6 +794,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={justWhitespaceDescription} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={justWhitespaceDescription.length} @@ -803,6 +827,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -834,6 +859,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -868,6 +894,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingUpdated} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingUpdated.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx index 743754842c6a8a..0d3a73a389050d 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx @@ -8,7 +8,7 @@ import { EuiButtonIcon, EuiLink } from '@elastic/eui'; import { omit } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; import { isUntitled } from '../helpers'; @@ -27,11 +27,9 @@ export const getCommonColumns = ({ itemIdToExpandedNotesRowMap, onOpenTimeline, onToggleShowNotes, - showExtendedColumnsAndActions, }: { onOpenTimeline: OnOpenTimeline; onToggleShowNotes: OnToggleShowNotes; - showExtendedColumnsAndActions: boolean; itemIdToExpandedNotesRowMap: Record; }) => [ { diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx index 13362e0f43a28c..4cbe1e45c473b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -35,6 +35,7 @@ describe('#getExtendedColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -66,6 +67,7 @@ describe('#getExtendedColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -99,6 +101,7 @@ describe('#getExtendedColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingUpdatedBy} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingUpdatedBy.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx index aa80a2b4e78a14..b6d874fa0c4d1a 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx @@ -6,7 +6,7 @@ /* eslint-disable react/display-name */ -import * as React from 'react'; +import React from 'react'; import { defaultToEmptyTag } from '../../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx index b6048b85eea753..31377d176acac9 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -30,6 +30,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -57,6 +58,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={with6Events} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={with6Events.length} @@ -82,6 +84,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -109,6 +112,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={with4Notes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={with4Notes.length} @@ -134,6 +138,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -161,6 +166,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={undefinedFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={undefinedFavorite.length} @@ -187,6 +193,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={nullFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={nullFavorite.length} @@ -213,6 +220,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptyFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptyFavorite.length} @@ -249,6 +257,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptyFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptyFavorite.length} @@ -289,6 +298,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptyFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptyFavorite.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx index 7649b1a8ce01cf..5b0f3ded7d71bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx @@ -7,7 +7,7 @@ /* eslint-disable react/display-name */ import { EuiIcon, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; import { getNotesCount, getPinnedEventCount } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx index d75863d1ccb8bd..26d9607a91fcdb 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -28,10 +28,11 @@ describe('TimelinesTable', () => { mockResults = cloneDeep(mockTimelineResults); }); - test('it renders the select all timelines header checkbox when showExtendedColumnsAndActions is true', () => { + test('it renders the select all timelines header checkbox when actionTimelineToShow has the action selectable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -59,10 +60,11 @@ describe('TimelinesTable', () => { ).toBe(true); }); - test('it does NOT render the select all timelines header checkbox when showExtendedColumnsAndActions is false', () => { + test('it does NOT render the select all timelines header checkbox when actionTimelineToShow has not the action selectable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -90,10 +92,11 @@ describe('TimelinesTable', () => { ).toBe(false); }); - test('it renders the Modified By column when showExtendedColumnsAndActions is true ', () => { + test('it renders the Modified By column when showExtendedColumns is true ', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -121,10 +124,11 @@ describe('TimelinesTable', () => { ).toContain(i18n.MODIFIED_BY); }); - test('it renders the notes column in the position of the Modified By column when showExtendedColumnsAndActions is false', () => { + test('it renders the notes column in the position of the Modified By column when showExtendedColumns is false', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -148,16 +152,17 @@ describe('TimelinesTable', () => { wrapper .find('thead tr th') .at(5) - .find('[data-test-subj="notes-count-header-icon"]') + .find('svg[data-test-subj="notes-count-header-icon"]') .first() .exists() ).toBe(true); }); - test('it renders the delete timeline (trash icon) when showExtendedColumnsAndActions is true', () => { + test('it renders the delete timeline (trash icon) when actionTimelineToShow has the delete action', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -185,10 +190,11 @@ describe('TimelinesTable', () => { ).toBe(true); }); - test('it does NOT render the delete timeline (trash icon) when showExtendedColumnsAndActions is false', () => { + test('it does NOT render the delete timeline (trash icon) when actionTimelineToShow has NOT the delete action', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -216,10 +222,11 @@ describe('TimelinesTable', () => { ).toBe(false); }); - test('it renders the rows per page selector when showExtendedColumnsAndActions is true', () => { + test('it renders the rows per page selector when showExtendedColumns is true', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -247,10 +254,11 @@ describe('TimelinesTable', () => { ).toBe(true); }); - test('it does NOT render the rows per page selector when showExtendedColumnsAndActions is false', () => { + test('it does NOT render the rows per page selector when showExtendedColumns is false', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -284,6 +292,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={defaultPageSize} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -311,10 +320,11 @@ describe('TimelinesTable', () => { ).toEqual('Rows per page: 123'); }); - test('it sorts the Last Modified column in descending order when showExtendedColumnsAndActions is true ', () => { + test('it sorts the Last Modified column in descending order when showExtendedColumns is true ', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -342,10 +352,11 @@ describe('TimelinesTable', () => { ).toContain(i18n.LAST_MODIFIED); }); - test('it sorts the Last Modified column in descending order when showExtendedColumnsAndActions is false ', () => { + test('it sorts the Last Modified column in descending order when showExtendedColumns is false ', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -376,6 +387,7 @@ describe('TimelinesTable', () => { test('it displays the expected message when no search results are found', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={[]} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={0} @@ -408,6 +420,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -446,6 +459,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -479,6 +493,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -510,6 +525,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx index 8f25b5345988a7..f09a9f6af048b5 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx @@ -5,11 +5,12 @@ */ import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from '../translations'; import { + ActionTimelineToShow, DeleteTimelines, OnOpenTimeline, OnSelectionChange, @@ -36,8 +37,8 @@ const BasicTable = styled(EuiBasicTable)` `; BasicTable.displayName = 'BasicTable'; -const getExtendedColumnsIfEnabled = (showExtendedColumnsAndActions: boolean) => - showExtendedColumnsAndActions ? [...getExtendedColumns()] : []; +const getExtendedColumnsIfEnabled = (showExtendedColumns: boolean) => + showExtendedColumns ? [...getExtendedColumns()] : []; /** * Returns the column definitions (passed as the `columns` prop to @@ -46,34 +47,36 @@ const getExtendedColumnsIfEnabled = (showExtendedColumnsAndActions: boolean) => * `Timelines` page */ const getTimelinesTableColumns = ({ + actionTimelineToShow, deleteTimelines, itemIdToExpandedNotesRowMap, onOpenTimeline, onToggleShowNotes, - showExtendedColumnsAndActions, + showExtendedColumns, }: { + actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; itemIdToExpandedNotesRowMap: Record; onOpenTimeline: OnOpenTimeline; onToggleShowNotes: OnToggleShowNotes; - showExtendedColumnsAndActions: boolean; + showExtendedColumns: boolean; }) => [ ...getCommonColumns({ itemIdToExpandedNotesRowMap, onOpenTimeline, onToggleShowNotes, - showExtendedColumnsAndActions, }), - ...getExtendedColumnsIfEnabled(showExtendedColumnsAndActions), + ...getExtendedColumnsIfEnabled(showExtendedColumns), ...getIconHeaderColumns(), ...getActionsColumns({ deleteTimelines, onOpenTimeline, - showDeleteAction: showExtendedColumnsAndActions, + actionTimelineToShow, }), ]; export interface TimelinesTableProps { + actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; defaultPageSize: number; loading: boolean; @@ -85,7 +88,7 @@ export interface TimelinesTableProps { pageIndex: number; pageSize: number; searchResults: OpenTimelineResult[]; - showExtendedColumnsAndActions: boolean; + showExtendedColumns: boolean; sortDirection: 'asc' | 'desc'; sortField: string; totalSearchResultsCount: number; @@ -97,6 +100,7 @@ export interface TimelinesTableProps { */ export const TimelinesTable = React.memo( ({ + actionTimelineToShow, deleteTimelines, defaultPageSize, loading: isLoading, @@ -108,13 +112,13 @@ export const TimelinesTable = React.memo( pageIndex, pageSize, searchResults, - showExtendedColumnsAndActions, + showExtendedColumns, sortField, sortDirection, totalSearchResultsCount, }) => { const pagination = { - hidePerPageOptions: !showExtendedColumnsAndActions, + hidePerPageOptions: !showExtendedColumns, pageIndex, pageSize, pageSizeOptions: [ @@ -142,16 +146,17 @@ export const TimelinesTable = React.memo( return ( ( noItemsMessage={i18n.ZERO_TIMELINES_MATCH} onChange={onTableChange} pagination={pagination} - selection={showExtendedColumnsAndActions ? selection : undefined} + selection={actionTimelineToShow.includes('selectable') ? selection : undefined} sorting={sorting} /> ); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx index 9303c09c994aa3..88dfab470ac962 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { EuiButtonProps } from '@elastic/eui'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { TitleRow } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx index 2e82c4979f41d9..c7de367e043640 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../translations'; import { OpenTimelineProps } from '../types'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts index 7bbefb9efa99e1..e5e85ccf0954a2 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts @@ -95,6 +95,8 @@ export interface OnTableChangeParams { /** Invoked by the EUI table implementation when the user interacts with the table */ export type OnTableChange = (tableChange: OnTableChangeParams) => void; +export type ActionTimelineToShow = 'duplicate' | 'delete' | 'selectable'; + export interface OpenTimelineProps { /** Invoked when the user clicks the delete (trash) icon on an individual timeline */ deleteTimelines?: DeleteTimelines; @@ -140,6 +142,8 @@ export interface OpenTimelineProps { title: string; /** The total (server-side) count of the search results */ totalSearchResultsCount: number; + /** Hide action on timeline if needed it */ + hideActions?: ActionTimelineToShow[]; } export interface UpdateTimeline { diff --git a/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx index 70e75cb54671ac..345701c97901f6 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../mock'; import { createStore, State } from '../../../store'; @@ -63,7 +62,7 @@ describe('AddFilterToGlobalSearchBar Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('Rendering tooltip', async () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx index ad1d80a7618543..5d2f3256ef5091 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../../mock'; @@ -19,6 +18,6 @@ describe('HistogramSignals', () => { ); - expect(toJson(wrapper.find('HistogramSignals'))).toMatchSnapshot(); + expect(wrapper.find('HistogramSignals')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx index 71e61e2425373c..d7c25e97b3838d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { apolloClientObservable, mockGlobalState } from '../../../../mock'; @@ -49,7 +48,7 @@ describe('Authentication Table Component', () => { ); - expect(toJson(wrapper.find('Connect(AuthenticationTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(AuthenticationTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx index 35c1eded18f15d..4a836333f3311c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx @@ -5,7 +5,7 @@ */ import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { render, act } from '@testing-library/react'; @@ -15,8 +15,6 @@ import { TestProviders } from '../../../../mock'; import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; -jest.mock('../../../../lib/kibana'); - describe('FirstLastSeen Component', () => { const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx index 830665f827301e..90cfe696610d94 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { HostOverview } from './index'; @@ -31,7 +30,7 @@ describe('Host Summary Component', () => { ); - expect(toJson(wrapper.find('HostOverview'))).toMatchSnapshot(); + expect(wrapper.find('HostOverview')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx index 4728925eb741a6..e561594013deaa 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { @@ -61,7 +60,7 @@ describe('Hosts Table', () => { ); - expect(toJson(wrapper.find('HostsTable'))).toMatchSnapshot(); + expect(wrapper.find('HostsTable')).toMatchSnapshot(); }); describe('Sorting on Table', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx index 577ec5ff514700..dc2340d42ebd90 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx @@ -7,7 +7,6 @@ import { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { KpiHostsComponentBase } from '.'; import * as statItems from '../../../stat_items'; import { kpiHostsMapping } from './kpi_hosts_mapping'; @@ -30,7 +29,7 @@ describe('kpiHostsComponent', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it should render KpiHostsData', () => { @@ -44,7 +43,7 @@ describe('kpiHostsComponent', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it should render KpiHostDetailsData', () => { @@ -58,7 +57,7 @@ describe('kpiHostsComponent', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx index 28ddb1df12c3a2..76fc2a0c389c38 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { hostsModel } from '../../../../store'; @@ -45,7 +44,7 @@ describe('Uncommon Process Table Component', () => { ); - expect(toJson(wrapper.find('UncommonProcessTable'))).toMatchSnapshot(); + expect(wrapper.find('UncommonProcessTable')).toMatchSnapshot(); }); test('it has a double dash (empty value) without any hosts at all', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx index 490efd08f0aa7d..e7b0d8e7d00d57 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx @@ -9,22 +9,23 @@ import React from 'react'; import { ScaleType } from '@elastic/charts'; import * as i18n from './translation'; import { MatrixHistogram } from '../../../matrix_histogram'; -import { bytesFormatter } from '../../../matrix_histogram/utils'; import { MatrixOverOrdinalHistogramData } from '../../../../graphql/types'; import { MatrixHistogramBasicProps } from '../../../matrix_histogram/types'; +import { useFormatBytes } from '../../../formatted_bytes'; export const NetworkDnsHistogram = ( props: MatrixHistogramBasicProps ) => { const dataKey = 'histogram'; const { ...matrixOverTimeProps } = props; + const formatBytes = useFormatBytes(); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx index c9d18d5f996f39..8c744c6573ee88 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock'; import { createStore, State } from '../../../../store'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx index 6c2edd764855c4..3038d7f41c632e 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ActionCreator } from 'typescript-fsa'; import { FlowTarget } from '../../../../graphql/types'; @@ -52,7 +51,7 @@ describe('IP Overview Component', () => { ); - expect(toJson(wrapper.find('IpOverview'))).toMatchSnapshot(); + expect(wrapper.find('IpOverview')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx index 964617c4c85b14..48d3b25f59e4a2 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { apolloClientObservable, mockGlobalState } from '../../../../mock'; @@ -42,7 +41,7 @@ describe('KpiNetwork Component', () => { ); - expect(toJson(wrapper.find('KpiNetworkComponent'))).toMatchSnapshot(); + expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); }); test('it renders the default widget', () => { @@ -59,7 +58,7 @@ describe('KpiNetwork Component', () => { ); - expect(toJson(wrapper.find('KpiNetworkComponent'))).toMatchSnapshot(); + expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx index b88653bcadde8a..e425057dd0f75f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -50,7 +49,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkDnsTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkDnsTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx index e39723f57f0b07..31a1b1667087a7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FlowDirection } from '../../../../graphql/types'; @@ -19,7 +18,7 @@ describe('NetworkTopNFlow Select direction', () => { test('it renders the basic switch to include PTR in table', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx index 81e0c7fad7b391..6ce87728ebff7e 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -51,7 +50,7 @@ describe('NetworkHttp Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkHttpTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkHttpTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx index 8fd245b0772431..764e440a5a4be8 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -59,7 +58,7 @@ describe('NetworkTopCountries Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopCountriesTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopCountriesTableComponent)')).toMatchSnapshot(); }); test('it renders the IP Details NetworkTopCountries table', () => { const wrapper = shallow( @@ -84,7 +83,7 @@ describe('NetworkTopCountries Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopCountriesTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopCountriesTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx index 5c4aa862283f20..24f68ef03d8917 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -59,7 +58,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopNFlowTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot(); }); test('it renders the default NetworkTopNFlow table on the IP Details page', () => { @@ -85,7 +84,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopNFlowTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx index 920d1cd8210e5a..81a472f3175e51 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -47,7 +46,7 @@ describe('Tls Table Component', () => { ); - expect(toJson(wrapper.find('Connect(TlsTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(TlsTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx index d01923f01543f4..b23c7bd504fb7d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -55,7 +54,7 @@ describe('Users Table Component', () => { ); - expect(toJson(wrapper.find('Connect(UsersTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(UsersTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx index d324df2ecb0c11..f99b2687d70728 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { OverviewHostStats } from '.'; import { mockData } from './mock'; @@ -15,7 +14,7 @@ describe('Overview Host Stat Data', () => { describe('rendering', () => { test('it renders the default OverviewHostStats', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); describe('loading', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx index 1b28c032221ab3..08093c5d38c151 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { OverviewNetworkStats } from '.'; import { mockData } from './mock'; @@ -17,7 +16,7 @@ describe('Overview Network Stat Data', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); describe('loading', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx index 0444360d2b965a..947bdee6a5cd23 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../graphql/types'; @@ -58,7 +57,7 @@ describe('Paginated Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the loading panel at the beginning ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/pin/index.tsx b/x-pack/legacy/plugins/siem/public/components/pin/index.tsx index 8aec3a60cbc371..9f898f9acaf2e9 100644 --- a/x-pack/legacy/plugins/siem/public/components/pin/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/pin/index.tsx @@ -6,7 +6,7 @@ import { EuiButtonIcon, IconSize } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../../components/timeline/body/translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx index 330385e39ca79f..6ab587f266a8a4 100644 --- a/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock/test_providers'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -20,7 +19,7 @@ describe('Port', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the port', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/port/index.tsx b/x-pack/legacy/plugins/siem/public/components/port/index.tsx index a8961a92f24cf7..bd6289547d0dc8 100644 --- a/x-pack/legacy/plugins/siem/public/components/port/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/port/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DefaultDraggable } from '../draggables'; import { getEmptyValue } from '../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx index 8ecc50402cef10..e5c39b365d9796 100644 --- a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { ProgressInline } from './index'; @@ -18,6 +17,6 @@ describe('ProgressInline', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx index e403963cbbe201..870d0b40d8cd48 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx @@ -14,8 +14,6 @@ import { FilterManager, SearchBar } from '../../../../../../../src/plugins/data/ import { QueryBar, QueryBarComponentProps } from '.'; import { createKibanaContextProviderMock } from '../../mock/kibana_react'; -jest.mock('../../lib/kibana'); - const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings; describe('QueryBar ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/resize_handle/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 38027f80e6684f..00000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Resizeable it renders 1`] = ` - - - - - -`; diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/index.test.tsx deleted file mode 100644 index f1580b59362bfa..00000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.test.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; - -import { TestProviders } from '../../mock/test_providers'; - -import { - addGlobalResizeCursorStyleToBody, - globalResizeCursorClassName, - removeGlobalResizeCursorStyleFromBody, - Resizeable, - calculateDeltaX, -} from '.'; -import { CommonResizeHandle } from './styled_handles'; - -describe('Resizeable', () => { - afterEach(() => { - document.body.classList.remove(globalResizeCursorClassName); - }); - - test('it applies the provided height to the ResizeHandleContainer when a height is specified', () => { - const wrapper = mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - expect(wrapper.find('[data-test-subj="resize-handle-container"]').first()).toHaveStyleRule( - 'height', - '100%' - ); - }); - - test('it applies positioning styles to the ResizeHandleContainer when positionAbsolute is true and bottom/left/right/top is specified', () => { - const wrapper = mount( - - } - id="test" - left={0} - onResize={jest.fn()} - positionAbsolute - render={() => <>} - right={0} - top={0} - /> - - ); - const resizeHandleContainer = wrapper - .find('[data-test-subj="resize-handle-container"]') - .first(); - - expect(resizeHandleContainer).toHaveStyleRule('bottom', '0'); - expect(resizeHandleContainer).toHaveStyleRule('left', '0'); - expect(resizeHandleContainer).toHaveStyleRule('position', 'absolute'); - expect(resizeHandleContainer).toHaveStyleRule('right', '0'); - expect(resizeHandleContainer).toHaveStyleRule('top', '0'); - }); - - test('it DOES NOT apply positioning styles to the ResizeHandleContainer when positionAbsolute is false, regardless if bottom/left/right/top is specified', () => { - const wrapper = mount( - - } - id="test" - left={0} - onResize={jest.fn()} - render={() => <>} - right={0} - top={0} - /> - - ); - const resizeHandleContainer = wrapper - .find('[data-test-subj="resize-handle-container"]') - .first(); - - expect(resizeHandleContainer).not.toHaveStyleRule('bottom', '0'); - expect(resizeHandleContainer).not.toHaveStyleRule('left', '0'); - expect(resizeHandleContainer).not.toHaveStyleRule('position', 'absolute'); - expect(resizeHandleContainer).not.toHaveStyleRule('right', '0'); - expect(resizeHandleContainer).not.toHaveStyleRule('top', '0'); - }); - - test('it renders', () => { - const wrapper = shallow( - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - ); - - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - describe('resize cursor styling', () => { - test('it does NOT apply the global-resize-cursor style to the body by default', () => { - mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - expect(document.body.className).not.toContain(globalResizeCursorClassName); - }); - - describe('#addGlobalResizeCursorStyleToBody', () => { - test('it adds the global-resize-cursor style to the body', () => { - mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - addGlobalResizeCursorStyleToBody(); - - expect(document.body.className).toContain(globalResizeCursorClassName); - }); - }); - - describe('#removeGlobalResizeCursorStyleFromBody', () => { - test('it removes the global-resize-cursor style from body', () => { - mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - addGlobalResizeCursorStyleToBody(); - removeGlobalResizeCursorStyleFromBody(); - - expect(document.body.className).not.toContain(globalResizeCursorClassName); - }); - }); - - describe('#calculateDeltaX', () => { - test('it returns 0 when prevX isEqual 0', () => { - expect(calculateDeltaX({ prevX: 0, screenX: 189 })).toEqual(0); - }); - - test('it returns positive difference when screenX > prevX', () => { - expect(calculateDeltaX({ prevX: 10, screenX: 189 })).toEqual(179); - }); - - test('it returns negative difference when prevX > screenX ', () => { - expect(calculateDeltaX({ prevX: 199, screenX: 189 })).toEqual(-10); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx deleted file mode 100644 index eb3326c2f2cd0c..00000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx +++ /dev/null @@ -1,145 +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, { useEffect, useRef } from 'react'; -import { fromEvent, Observable, Subscription } from 'rxjs'; -import { concatMap, takeUntil } from 'rxjs/operators'; -import styled from 'styled-components'; - -export type OnResize = ({ delta, id }: { delta: number; id: string }) => void; - -export const resizeCursorStyle = 'col-resize'; -export const globalResizeCursorClassName = 'global-resize-cursor'; - -/** This polyfill is for Safari and IE-11 only. `movementX` is more accurate and "feels" better, so only use this function on Safari and IE-11 */ -export const calculateDeltaX = ({ prevX, screenX }: { prevX: number; screenX: number }) => - prevX !== 0 ? screenX - prevX : 0; - -const isSafari = /^((?!chrome|android|crios|fxios|Firefox).)*safari/i.test(navigator.userAgent); - -interface ResizeHandleContainerProps { - bottom?: string | number; - /** optionally provide a height style ResizeHandleContainer */ - height?: string; - left?: string | number; - positionAbsolute?: boolean; - right?: string | number; - top?: string | number; -} - -interface Props extends ResizeHandleContainerProps { - /** a (styled) resize handle */ - handle: React.ReactNode; - /** the `onResize` callback will be invoked with this id */ - id: string; - /** invoked when the handle is resized */ - onResize: OnResize; - /** The resizeable content to render */ - render: (isResizing: boolean) => React.ReactNode; -} - -const ResizeHandleContainer = styled.div` - bottom: ${({ positionAbsolute, bottom }) => positionAbsolute && bottom}; - cursor: ${resizeCursorStyle}; - height: ${({ height }) => height}; - left: ${({ positionAbsolute, left }) => positionAbsolute && left}; - position: ${({ positionAbsolute }) => positionAbsolute && 'absolute'}; - right: ${({ positionAbsolute, right }) => positionAbsolute && right}; - top: ${({ positionAbsolute, top }) => positionAbsolute && top}; - z-index: ${({ positionAbsolute, theme }) => positionAbsolute && theme.eui.euiZLevel1}; -`; -ResizeHandleContainer.displayName = 'ResizeHandleContainer'; - -export const addGlobalResizeCursorStyleToBody = () => { - document.body.classList.add(globalResizeCursorClassName); -}; - -export const removeGlobalResizeCursorStyleFromBody = () => { - document.body.classList.remove(globalResizeCursorClassName); -}; - -export const Resizeable = React.memo( - ({ bottom, handle, height, id, left, onResize, positionAbsolute, render, right, top }) => { - const drag$ = useRef | null>(null); - const dragEventTargets = useRef>([]); - const dragSubscription = useRef(null); - const prevX = useRef(0); - const ref = useRef(null); - const upSubscription = useRef(null); - const isResizingRef = useRef(false); - - const calculateDelta = (e: MouseEvent) => { - const deltaX = calculateDeltaX({ prevX: prevX.current, screenX: e.screenX }); - prevX.current = e.screenX; - return deltaX; - }; - useEffect(() => { - const move$ = fromEvent(document, 'mousemove'); - const down$ = fromEvent(ref.current!, 'mousedown'); - const up$ = fromEvent(document, 'mouseup'); - - drag$.current = down$.pipe(concatMap(() => move$.pipe(takeUntil(up$)))); - dragSubscription.current = - drag$.current && - drag$.current.subscribe(event => { - // We do a feature detection of event.movementX here and if it is missing - // we calculate the delta manually. Browsers IE-11 and Safari will call calculateDelta - const delta = - event.movementX == null || isSafari ? calculateDelta(event) : event.movementX; - if (!isResizingRef.current) { - isResizingRef.current = true; - } - onResize({ id, delta }); - if (event.target != null && event.target instanceof HTMLElement) { - const htmlElement: HTMLElement = event.target; - dragEventTargets.current = [ - ...dragEventTargets.current, - { htmlElement, prevCursor: htmlElement.style.cursor }, - ]; - htmlElement.style.cursor = resizeCursorStyle; - } - }); - - upSubscription.current = up$.subscribe(() => { - if (isResizingRef.current) { - dragEventTargets.current.reverse().forEach(eventTarget => { - eventTarget.htmlElement.style.cursor = eventTarget.prevCursor; - }); - dragEventTargets.current = []; - isResizingRef.current = false; - } - }); - return () => { - if (dragSubscription.current != null) { - dragSubscription.current.unsubscribe(); - } - if (upSubscription.current != null) { - upSubscription.current.unsubscribe(); - } - }; - }, []); - - return ( - <> - {render(isResizingRef.current)} - - {handle} - - - ); - } -); - -Resizeable.displayName = 'Resizeable'; diff --git a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx index 988bb13841fa52..18040937328616 100644 --- a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { globalNode, HookWrapper } from '../../mock'; import { useScrollToTop } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx index 95c68d0233c691..430cffa520c005 100644 --- a/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx @@ -5,15 +5,14 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { SelectableText } from '.'; describe('SelectableText', () => { test('renders correctly against snapshot', () => { const wrapper = shallow({'You may select this text'}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it applies the user-select: text style', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx index de463f8e29f916..1fdcd8eee941fd 100644 --- a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -14,7 +13,7 @@ import { SkeletonRow } from './index'; describe('SkeletonRow', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the correct number of cells if cellCount is specified', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx index edf3dc08c7282a..baeca10ee0fae6 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { get, uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DefaultDraggable } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx index c437994145d63b..3dee668d66a707 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx @@ -6,9 +6,8 @@ import numeral from '@elastic/numeral'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { asArrayIfExists } from '../../lib/helpers'; import { getMockNetflowData } from '../../mock'; @@ -101,7 +100,7 @@ describe('SourceDestination', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(
{getSourceDestinationInstance()}
); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a destination label', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx index 0333181c3521c5..c994c4b2cd5197 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Network } from './network'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx index 4ec317737e72d9..ea6ce4caa72702 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Ip } from '../ip'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx index cfacbb077856c5..a0b86b3e9a1338 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DirectionBadge } from '../direction'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx index 0675212591a66f..005ebc14dcdcc6 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { ArrowBody, ArrowHead } from '../arrows'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx index 463373b5894f1d..60ab59c3796ff0 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx @@ -5,7 +5,7 @@ */ import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { asArrayIfExists } from '../../lib/helpers'; import { getMockNetflowData } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx index ebc5beaa4e3548..33159387214e43 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx @@ -6,7 +6,7 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty, isEqual, uniqWith } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../ip'; import { DESTINATION_PORT_FIELD_NAME, SOURCE_PORT_FIELD_NAME, Port } from '../port'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx index 53dade22351bfb..d6a3ce41587341 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { SourceDestinationArrows } from './source_destination_arrows'; import { SourceDestinationIp } from './source_destination_ip'; diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx index 7dac5ff2e9bb66..e68cf47500555b 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/index.test.tsx @@ -8,8 +8,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, ReactWrapper } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { @@ -93,7 +92,7 @@ describe('Stat Items Component', () => { ], ])('disable charts', wrapper => { test('it renders the default widget', () => { - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render titles', () => { @@ -182,7 +181,7 @@ describe('Stat Items Component', () => { ); }); test('it renders the default widget', () => { - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should handle multiple titles', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx index 3424c05f32d637..155b219c04b920 100644 --- a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -15,7 +14,7 @@ describe('Subtitle', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders one subtitle string item', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx index 013104da7c6128..c5838fa283e177 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { useUiSetting$ } from '../../lib/kibana'; diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx index d864d4306b8ef6..17c7c0bac8fa21 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx @@ -10,9 +10,8 @@ import { getRowItemDraggable, OverflowFieldComponent, } from './helpers'; -import * as React from 'react'; +import React from 'react'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { TestProviders } from '../../mock'; import { getEmptyValue } from '../empty_value'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -29,7 +28,7 @@ describe('Table Helpers', () => { idPrefix: 'idPrefix', }); const wrapper = shallow({rowItem}); - expect(toJson(wrapper.find('DraggableWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DraggableWrapper')).toMatchSnapshot(); }); test('it returns empty value when rowItem is undefined', () => { @@ -97,7 +96,7 @@ describe('Table Helpers', () => { idPrefix: 'idPrefix', }); const wrapper = shallow({rowItems}); - expect(toJson(wrapper.find('DragDropContext'))).toMatchSnapshot(); + expect(wrapper.find('DragDropContext')).toMatchSnapshot(); }); test('it returns empty value when rowItems is undefined', () => { @@ -193,7 +192,7 @@ describe('Table Helpers', () => { test('it returns correctly against snapshot', () => { const rowItemOverflow = getRowItemOverflow(items, 'attrName', 1, 1); const wrapper = shallow(
{rowItemOverflow}
); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it does not show "more not shown" when maxOverflowItems are not exceeded', () => { @@ -215,7 +214,7 @@ describe('Table Helpers', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it does not truncates as per custom overflowLength value', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx index 6c793126efa726..c2dfda6a81ce40 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx @@ -11,7 +11,7 @@ import { EuiGlobalToastListToast as Toast, } from '@elastic/eui'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx index a9628ebbd183f7..9351fddd90dd5a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx index 54b1fb0893c83b..6cf14cd972d3ec 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Note } from '../../../../lib/note'; import { AssociateNote, UpdateNote } from '../../../notes/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 1b66a130c3550c..dfea99ffd70910 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -1,20 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` - - - + - - + - - - - + + + - - + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx index b57b343d614a80..64e8aa3c7e7b7c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { OnColumnRemoved } from '../../../events'; import { EventsHeadingExtra, EventsLoading } from '../../../styles'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx index 15911f522032a3..ccaeeff972a812 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx @@ -4,6 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { Draggable } from 'react-beautiful-dnd'; +import { Resizable, ResizeCallback } from 're-resizable'; +import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; +import { getDraggableFieldId, DRAG_TYPE_FIELD } from '../../../drag_and_drop/helpers'; +import { DraggableFieldBadge } from '../../../draggables/field_badge'; +import { OnColumnRemoved, OnColumnSorted, OnFilterChange, OnColumnResized } from '../../events'; +import { EventsTh, EventsThContent, EventsHeadingHandle } from '../../styles'; +import { Sort } from '../sort'; +import { DraggingContainer } from './common/dragging_container'; + +import { Header } from './header'; import { ColumnId } from '../column_id'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; @@ -22,3 +34,90 @@ export interface ColumnHeader { type?: string; width: number; } + +interface ColumneHeaderProps { + draggableIndex: number; + header: ColumnHeader; + onColumnRemoved: OnColumnRemoved; + onColumnSorted: OnColumnSorted; + onColumnResized: OnColumnResized; + onFilterChange?: OnFilterChange; + sort: Sort; + timelineId: string; +} + +const ColumnHeaderComponent: React.FC = ({ + draggableIndex, + header, + timelineId, + onColumnRemoved, + onColumnResized, + onColumnSorted, + onFilterChange, + sort, +}) => { + const [isDragging, setIsDragging] = React.useState(false); + const handleResizeStop: ResizeCallback = (e, direction, ref, delta) => { + onColumnResized({ columnId: header.id, delta: delta.width }); + }; + + return ( + , + }} + onResizeStop={handleResizeStop} + > + + {(dragProvided, dragSnapshot) => ( + + {!dragSnapshot.isDragging ? ( + +
+ + ) : ( + + + + + + )} + + )} + + + ); +}; + +export const ColumnHeader = React.memo(ColumnHeaderComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/common/dragging_container.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/common/dragging_container.tsx new file mode 100644 index 00000000000000..21aa17aa1c52c5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/common/dragging_container.tsx @@ -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 { FC, memo, useEffect } from 'react'; + +interface DraggingContainerProps { + children: JSX.Element; + onDragging: Function; +} + +const DraggingContainerComponent: FC = ({ children, onDragging }) => { + useEffect(() => { + onDragging(true); + + return () => onDragging(false); + }); + + return children; +}; + +export const DraggingContainer = memo(DraggingContainerComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx index 057f751f451ac0..853c1ec24b7031 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx @@ -5,7 +5,7 @@ */ import { EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Pin } from '../../../../pin'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx index 634db2dc526761..87a1035fc07397 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx @@ -6,7 +6,7 @@ import { EuiCheckbox, EuiSuperSelect } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import { getEventsSelectOptions } from './helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx index d93983d7d40545..b9cfee395bafb3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ColumnHeaderType } from '../column_header'; import { defaultHeaders } from '../default_headers'; @@ -24,7 +23,7 @@ describe('Filter', () => { }; const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('rendering', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx index c56322cc69e0ca..0b5247e7da6786 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx @@ -5,7 +5,7 @@ */ import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { OnFilterChange } from '../../../events'; import { ColumnHeader } from '../column_header'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap index 64c2b6ed106929..d30054ae1a3fe4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap @@ -1,14 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header renders correctly against snapshot 1`] = ` -} - id="@timestamp" - onResize={[Function]} - positionAbsolute={true} - render={[Function]} - right="-1px" - top={0} -/> + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/header_content.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/header_content.tsx new file mode 100644 index 00000000000000..c38ae26050c937 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/header_content.tsx @@ -0,0 +1,76 @@ +/* + * 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 { EuiToolTip } from '@elastic/eui'; +import { noop } from 'lodash/fp'; +import React from 'react'; + +import { TruncatableText } from '../../../../truncatable_text'; +import { EventsHeading, EventsHeadingTitleButton, EventsHeadingTitleSpan } from '../../../styles'; +import { useTimelineContext } from '../../../timeline_context'; +import { Sort } from '../../sort'; +import { SortIndicator } from '../../sort/sort_indicator'; +import { ColumnHeader } from '../column_header'; +import { HeaderToolTipContent } from '../header_tooltip_content'; +import { getSortDirection } from './helpers'; + +interface HeaderContentProps { + children: React.ReactNode; + header: ColumnHeader; + isResizing: boolean; + onClick: () => void; + sort: Sort; +} + +const HeaderContentComponent: React.FC = ({ + children, + header, + isResizing, + onClick, + sort, +}) => { + const isLoading = useTimelineContext(); + + return ( + + {header.aggregatable ? ( + + + } + > + <>{header.label ?? header.id} + + + + + + ) : ( + + + } + > + <>{header.label ?? header.id} + + + + )} + + {children} + + ); +}; + +export const HeaderContent = React.memo(HeaderContentComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx index bfcf3cd6397999..fab2e7ee872bf1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../../../graphql/types'; import { TestProviders } from '../../../../../mock'; @@ -33,14 +32,12 @@ describe('Header', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('rendering', () => { @@ -50,9 +47,7 @@ describe('Header', () => { @@ -75,9 +70,7 @@ describe('Header', () => { @@ -99,9 +92,7 @@ describe('Header', () => { @@ -127,9 +118,7 @@ describe('Header', () => { @@ -154,9 +143,7 @@ describe('Header', () => { @@ -182,9 +169,7 @@ describe('Header', () => { @@ -202,9 +187,7 @@ describe('Header', () => { @@ -222,9 +205,7 @@ describe('Header', () => { @@ -335,9 +316,7 @@ describe('Header', () => { @@ -358,9 +337,7 @@ describe('Header', () => { @@ -370,25 +347,4 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="header-tooltip"]').exists()).toEqual(true); }); }); - - describe('setIsResizing', () => { - test('setIsResizing have been call when it renders actions', () => { - const mockSetIsResizing = jest.fn(); - mount( - - - - ); - - expect(mockSetIsResizing).toHaveBeenCalled(); - }); - }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx index 311b4bfda60fe6..c45b9ce425debe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx @@ -4,103 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiToolTip } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import React from 'react'; +import React, { useCallback } from 'react'; -import { OnResize, Resizeable } from '../../../../resize_handle'; -import { TruncatableText } from '../../../../truncatable_text'; -import { OnColumnRemoved, OnColumnResized, OnColumnSorted, OnFilterChange } from '../../../events'; -import { - EventsHeading, - EventsHeadingHandle, - EventsHeadingTitleButton, - EventsHeadingTitleSpan, -} from '../../../styles'; -import { useTimelineContext } from '../../../timeline_context'; +import { OnColumnRemoved, OnColumnSorted, OnFilterChange } from '../../../events'; import { Sort } from '../../sort'; -import { SortIndicator } from '../../sort/sort_indicator'; import { Actions } from '../actions'; import { ColumnHeader } from '../column_header'; import { Filter } from '../filter'; -import { HeaderToolTipContent } from '../header_tooltip_content'; -import { getNewSortDirectionOnClick, getSortDirection } from './helpers'; - -interface HeaderCompProps { - children: React.ReactNode; - header: ColumnHeader; - isResizing: boolean; - onClick: () => void; - sort: Sort; -} - -const HeaderComp = React.memo( - ({ children, header, isResizing, onClick, sort }) => { - const isLoading = useTimelineContext(); - - return ( - - {header.aggregatable ? ( - - - } - > - <>{header.label ?? header.id} - - - - - - ) : ( - - - } - > - <>{header.label ?? header.id} - - - - )} - - {children} - - ); - } -); -HeaderComp.displayName = 'HeaderComp'; +import { getNewSortDirectionOnClick } from './helpers'; +import { HeaderContent } from './header_content'; interface Props { header: ColumnHeader; onColumnRemoved: OnColumnRemoved; - onColumnResized: OnColumnResized; onColumnSorted: OnColumnSorted; onFilterChange?: OnFilterChange; - setIsResizing: (isResizing: boolean) => void; sort: Sort; timelineId: string; } -/** Renders a header */ -export const HeaderComponent = ({ +export const HeaderComponent: React.FC = ({ header, onColumnRemoved, - onColumnResized, onColumnSorted, onFilterChange = noop, - setIsResizing, sort, -}: Props) => { - const onClick = () => { +}) => { + const onClick = useCallback(() => { onColumnSorted!({ columnId: header.id, sortDirection: getNewSortDirectionOnClick({ @@ -108,41 +39,17 @@ export const HeaderComponent = ({ currentSort: sort, }), }); - }; - - const onResize: OnResize = ({ delta, id }) => { - onColumnResized({ columnId: id, delta }); - }; - - const renderActions = (isResizing: boolean) => { - setIsResizing(isResizing); - return ( - <> - - - - - - - ); - }; + }, [onColumnSorted, header, sort]); return ( - } - id={header.id} - onResize={onResize} - positionAbsolute - render={renderActions} - right="-1px" - top={0} - /> + <> + + + + + + ); }; -HeaderComponent.displayName = 'HeaderComponent'; - export const Header = React.memo(HeaderComponent); - -Header.displayName = 'Header'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx index da348590bd0444..20c139ae1d0503 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { defaultHeaders } from '../../../../../mock'; import { ColumnHeader } from '../column_header'; @@ -89,6 +88,6 @@ describe('HeaderToolTipContent', () => { test('it renders the expected table content', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx index a63ec2bf840a6b..5deb2c3e66376f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx @@ -6,7 +6,7 @@ import { EuiIcon } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { getIconFromType } from '../../../../event_details/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx index 7765b0360d35b7..4b97dd7573a455 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../helpers'; import { defaultHeaders } from './default_headers'; @@ -18,14 +17,6 @@ import { useMountAppended } from '../../../../utils/use_mount_appended'; import { ColumnHeadersComponent } from '.'; -jest.mock('../../../resize_handle/is_resizing', () => ({ - ...jest.requireActual('../../../resize_handle/is_resizing'), - useIsContainerResizing: () => ({ - isResizing: true, - setIsResizing: jest.fn(), - }), -})); - describe('ColumnHeaders', () => { const mount = useMountAppended(); @@ -54,7 +45,7 @@ describe('ColumnHeaders', () => { toggleColumn={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the field browser', () => { @@ -118,37 +109,5 @@ describe('ColumnHeaders', () => { ).toContain(h.id); }); }); - - test('it disables dragging during a column resize', () => { - const wrapper = mount( - - - - ); - - defaultHeaders.forEach(h => { - expect( - wrapper - .find('[data-test-subj="draggable"]') - .first() - .prop('isDragDisabled') - ).toBe(true); - }); - }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx index 6b53b763b77835..953ffb4d4932b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx @@ -6,20 +6,13 @@ import { EuiCheckbox } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; -import { Draggable, Droppable } from 'react-beautiful-dnd'; +import React from 'react'; +import { Droppable } from 'react-beautiful-dnd'; import { BrowserFields } from '../../../../containers/source'; -import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; -import { - DRAG_TYPE_FIELD, - droppableTimelineColumnsPrefix, - getDraggableFieldId, -} from '../../../drag_and_drop/helpers'; -import { DraggableFieldBadge } from '../../../draggables/field_badge'; +import { DRAG_TYPE_FIELD, droppableTimelineColumnsPrefix } from '../../../drag_and_drop/helpers'; import { StatefulFieldsBrowser } from '../../../fields_browser'; import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers'; -import { useIsContainerResizing } from '../../../resize_handle/is_resizing'; import { OnColumnRemoved, OnColumnResized, @@ -39,7 +32,6 @@ import { import { Sort } from '../sort'; import { ColumnHeader } from './column_header'; import { EventsSelect } from './events_select'; -import { Header } from './header'; interface Props { actionsColumnWidth: number; @@ -78,132 +70,86 @@ export const ColumnHeadersComponent = ({ sort, timelineId, toggleColumn, -}: Props) => { - const { isResizing, setIsResizing } = useIsContainerResizing(); - - return ( - - - - {showEventsSelect && ( - - - - - - )} - - {showSelectAllCheckbox && ( - - - ) => { - onSelectAll({ isSelected: event.currentTarget.checked }); - }} - /> - - - )} - +}: Props) => ( + + + + {showEventsSelect && ( - - + + + + )} + {showSelectAllCheckbox && ( + + + ) => { + onSelectAll({ isSelected: event.currentTarget.checked }); + }} /> - + )} + + + + + + - - {dropProvided => ( + + {(dropProvided, snapshot) => ( + <> - {columnHeaders.map((header, i) => ( - ( + - {(dragProvided, dragSnapshot) => ( - - {!dragSnapshot.isDragging ? ( - -
- - ) : ( - - - - )} - - )} - + draggableIndex={draggableIndex} + timelineId={timelineId} + header={header} + onColumnRemoved={onColumnRemoved} + onColumnSorted={onColumnSorted} + onFilterChange={onFilterChange} + onColumnResized={onColumnResized} + sort={sort} + /> ))} - )} - - - - ); -}; - -ColumnHeadersComponent.displayName = 'ColumnHeadersComponent'; + {dropProvided.placeholder} + + )} + + + +); export const ColumnHeaders = React.memo(ColumnHeadersComponent); - -ColumnHeaders.displayName = 'ColumnHeaders'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx index d919bdb0788a62..12ce3bb7092423 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { RangePicker } from '.'; import { Ranges } from './ranges'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx index c221829f92d041..de21fdac6434a5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx @@ -5,7 +5,7 @@ */ import { EuiSelect } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { OnRangeSelected } from '../../../events'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx index c6b1ee056ebcc5..4378a96b2919a0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { DEFAULT_PLACEHOLDER, TextFilter } from '.'; @@ -14,7 +13,7 @@ describe('TextFilter', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('placeholder', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx index 09672eb9a38fe5..fcc23314a18136 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx @@ -6,7 +6,7 @@ import { EuiFieldText } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { OnFilterChange } from '../../../events'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index a168f8d48fa33b..75c05dd1455afd 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Columns it renders the expected columns 1`] = ` - - - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + - - - - + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx index f5b33296561c7e..36427015260a7f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockTimelineData } from '../../../../mock'; import { defaultHeaders } from '../column_headers/default_headers'; @@ -29,6 +28,6 @@ describe('Columns', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx index 03900e1745236d..37b6e302150560 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { OnColumnResized } from '../../events'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx index 9ea1bbb1e84302..a39c254c61126b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import uuid from 'uuid'; import { TimelineNonEcsData } from '../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx index 5c6a0872ce3406..239d8a9d779168 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../containers/source/mock'; import { Direction } from '../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx index ad904554e33ad3..53a20544124400 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { useMountAppended } from '../../../../utils/use_mount_appended'; import { TestProviders } from '../../../../mock'; @@ -25,7 +24,7 @@ describe('Args', () => { processTitle="process-title-1" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns an empty string when both args and process title are undefined', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx index 8282931aa05794..22367ec879851a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx index 8d3a7133690313..21cccc88f4fbce 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -30,7 +29,7 @@ describe('GenericDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns auditd if the data does contain auditd data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx index b60d89c857421b..c25c656b75e41f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx index af4971faae2956..fce0e1d645e164 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -31,7 +30,7 @@ describe('GenericFileDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns auditd if the data does contain auditd data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx index be84696033d69f..797361878e6c53 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer, IconType } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx index f2cce8b16b5d6a..b78d9261849cb8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -46,7 +45,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a auditd datum', () => { @@ -125,7 +124,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a auditd datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx index a5b861be08e563..598769e854b422 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { PrimarySecondaryUserInfo, nilOrUnSet } from './primary_secondary_user_info'; @@ -26,7 +25,7 @@ describe('UserPrimarySecondary', () => { secondary="secondary-1" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render user name only if that is all that is present', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx index bd350a599bb47f..a54042d3de9d87 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx index 2240d83169e0da..a0a9977f5765e3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx @@ -6,8 +6,7 @@ import { EuiFlexItem } from '@elastic/eui'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { SessionUserHostWorkingDir } from './session_user_host_working_dir'; @@ -32,7 +31,7 @@ describe('SessionUserHostWorkingDir', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders with just eventId and contextId', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx index 3c825e6c931be3..6a6b55bb817c84 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx index 07207c822ad291..a7c9d10e82a2fd 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { mockBrowserFields } from '../../../../../../public/containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx index 752163901de2e2..824e8c00de307a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx @@ -6,7 +6,7 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Details } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx index d6c50460194a78..e12eacd73559de 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx index fd49395379e240..c7a08620bebbbe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx index 04f7cd9b560a69..b31d01b8e94a0f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; @@ -36,7 +35,7 @@ describe('empty_column_renderer', () => { timelineId: 'test', }); const wrapper = shallow({emptyColumn}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return isInstance true if source is empty', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx index 8e046342abd42c..7e2346ced8785e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx @@ -6,7 +6,7 @@ /* eslint-disable react/display-name */ -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { DraggableWrapper, DragEffects } from '../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx index 77569f07a23c23..72b879d4ade78b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { mockBrowserFields } from '../../../../../../public/containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx index 10f9c4ad9e5455..35a88f52f05a3a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx @@ -6,7 +6,7 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx index 45a824e034b15a..4e522f6ed5c94f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx index 185e9c9a8287a2..c2c42ba0e4ddcf 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx index 21fbafb64d57f6..4da236bfa34c30 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { useMountAppended } from '../../../../utils/use_mount_appended'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx index 9ea5f2cdd99fa9..7671e3f0509a53 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx index ff63d02acc37c7..d800821f8d8a5f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx index a1c43f3ecb1633..e4871c6479c6be 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx index 1e7c3bfedc9b9c..73f7b004ca3f71 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { get } from 'lodash/fp'; import React from 'react'; @@ -33,7 +32,7 @@ describe('Events', () => { /> ); - expect(toJson(wrapper.find('FormattedFieldValue'))).toMatchSnapshot(); + expect(wrapper.find('FormattedFieldValue')).toMatchSnapshot(); }); test('it renders a localized date tooltip for a field type of date that has a valid timestamp', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx index d445ec2859e2c2..25d5c71caf48ab 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { mockTimelineData } from '../../../../mock'; @@ -41,7 +40,7 @@ describe('get_column_renderer', () => { }); const wrapper = shallow({column}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render event severity when dealing with data that is not suricata', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx index bea525116021d5..f367769b78f40d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../containers/source/mock'; import { Ecs } from '../../../../graphql/types'; @@ -44,7 +43,7 @@ describe('get_column_renderer', () => { }); const wrapper = shallow({row}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render plain row data when it is a non suricata row', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx index 6c58b1ec6f35c9..d84dfcc5618826 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { mockTimelineData, TestProviders } from '../../../../mock'; @@ -24,7 +23,7 @@ describe('HostWorkingDir', () => { workingDirectory="[working-directory-123]" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a hostname without a workingDirectory', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx index 0bdecfecd6c594..db49df30be473f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx index 0904c836c2f307..0990301b6e2b92 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx @@ -5,7 +5,7 @@ */ import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Ecs } from '../../../../graphql/types'; import { asArrayIfExists } from '../../../../lib/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx index 6ba8f3f28dae85..68629a9a70058c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -38,7 +37,7 @@ describe('netflowRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('#isInstance', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx index 80ae10a48415cf..684def7386da04 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx index 7cb6c3704a238b..1402743ef8a519 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx index 008885b5264c88..8a22307767a40b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx @@ -5,23 +5,17 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; -import { defaultHeaders, mockFrameworks, mockTimelineData, TestProviders } from '../../../../mock'; +import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../mock'; import { getEmptyValue } from '../../../empty_value'; import { useMountAppended } from '../../../../utils/use_mount_appended'; import { plainColumnRenderer } from './plain_column_renderer'; import { getValues, deleteItemIdx, findItem } from './helpers'; -jest.mock('../../../../lib/kibana'); - -const mockFramework = mockFrameworks.default_UTC; - describe('plain_column_renderer', () => { const mount = useMountAppended(); @@ -41,7 +35,7 @@ describe('plain_column_renderer', () => { timelineId: 'test', }); const wrapper = shallow({column}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return isInstance false if source is empty', () => { @@ -134,11 +128,7 @@ describe('plain_column_renderer', () => { {column} ); - expect(wrapper.text()).toEqual( - moment - .tz(getValues('@timestamp', mockDatum)![0], mockFramework.dateFormatTz!) - .format(mockFramework.dateFormat) - ); + expect(wrapper.text()).toEqual('Nov 5, 2018 @ 19:03:25.937'); }); test('should return an empty value if destination ip is empty', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx index 355f3fb248238e..50ea7ca05b921a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash'; import React from 'react'; import { ThemeProvider } from 'styled-components'; @@ -30,7 +29,7 @@ describe('plain_row_renderer', () => { timelineId: 'test', }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should always return isInstance true', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx index ff1cb60db0d93f..8cc7323ed358f8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { ProcessDraggable, ProcessDraggableWithNonExistentProcess } from './process_draggable'; @@ -28,7 +27,7 @@ describe('ProcessDraggable', () => { processPid={123} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if everything is null', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx index 29d6c0e7d59c2b..35512c60629dd0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx index cb0b40bdd8fcae..b6696d38dc1c53 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx index 3f77726474c568..027aa0df8bcdd3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { mockTimelineData } from '../../../../../mock'; @@ -26,7 +25,7 @@ describe('SuricataDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns text if the data does contain suricata data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx index 35733e5e0b31b9..17f5f236265edf 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx @@ -6,7 +6,7 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx index 6fe6523180e584..dd773bb88ef686 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { ExternalLinkIcon } from '../../../../external_link_icon'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx index 66c9613c029958..170d17e8e279ea 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { Ecs } from '../../../../../graphql/types'; @@ -35,7 +34,7 @@ describe('suricata_row_renderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a suricata datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx index 4eefb4b0bc8b9a..beae16af558ed5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { useMountAppended } from '../../../../../utils/use_mount_appended'; @@ -30,7 +29,7 @@ describe('SuricataSignature', () => { signature="ET SCAN ATTACK Hello" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx index 632e8ff35950e8..2b9adfe21b120b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DragEffects, DraggableWrapper } from '../../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx index e42a91b7d7972c..4e4e1a0b7bf6fa 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { AuthSsh } from './auth_ssh'; @@ -21,7 +20,7 @@ describe('AuthSsh', () => { sshMethod="[ssh-method]" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if sshSignature and sshMethod are both null', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx index 60eab8381e98d6..0ff2eec35314d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx index 54f5b2f1652874..19113d93f7cb03 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -30,7 +29,7 @@ describe('SystemGenericDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns system rendering if the data does contain system data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx index e3627d0ec918a7..e1524c8e5aecbb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx index d4260d9bd183a1..cab7191c13aef5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -30,7 +29,7 @@ describe('SystemGenericFileDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns system rendering if the data does contain system data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx index 401c1c522ca602..c47d9603cbea26 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx index b2dbdb6b0e45cb..5f809d595f1b05 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -49,8 +48,6 @@ import { } from './generic_row_renderer'; import * as i18n from './translations'; -jest.mock('../../../../../lib/kibana'); - describe('GenericRowRenderer', () => { const mount = useMountAppended(); @@ -77,7 +74,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a system datum', () => { @@ -141,7 +138,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a auditd datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx index 167abe2185bcc5..100c8fbe5a9888 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { useMountAppended } from '../../../../../utils/use_mount_appended'; @@ -26,7 +25,7 @@ describe('Package', () => { packageVersion="package-version-123" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if all of the package information is null ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx index d87639d2b8d6ed..a28e850e2af968 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx index 41a71f55cae19f..73d1d5cb441ef3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash'; import React from 'react'; import { ThemeProvider } from 'styled-components'; @@ -34,7 +33,7 @@ describe('unknown_column_renderer', () => { timelineId: 'test', }); const wrapper = shallow({emptyColumn}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return isInstance true with a made up column name', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx index 662392078f38ae..45b670acb569ab 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { UserHostWorkingDir } from './user_host_working_dir'; @@ -27,7 +26,7 @@ describe('UserHostWorkingDir', () => { workingDirectory="[working-directory-123]" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if userDomain, userName, hostName, and workingDirectory are all null', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx index 281cfd39bd9d24..d370afee2585f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; import { TokensFlexItem } from './helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx index 73b90410bc8039..7617a01acf1d91 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../mock'; @@ -26,7 +25,7 @@ describe('ZeekDetails', () => { /> ); - expect(toJson(wrapper.find('ZeekDetails'))).toMatchSnapshot(); + expect(wrapper.find('ZeekDetails')).toMatchSnapshot(); }); test('it returns zeek.connection if the data does contain zeek.connection data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx index 1a31e560d78104..d8561186b45462 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx @@ -5,7 +5,7 @@ */ import { EuiSpacer } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx index b4fd9a978b7d88..4242308a55a646 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { Ecs } from '../../../../../graphql/types'; @@ -34,7 +33,7 @@ describe('zeek_row_renderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a zeek datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx index 4ef2bb89e05ca4..c09bd6b7a356de 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Ecs } from '../../../../../graphql/types'; import { mockTimelineData, TestProviders } from '../../../../../mock'; @@ -37,7 +36,7 @@ describe('ZeekSignature', () => { describe('rendering', () => { test('it renders the default Zeek', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx index 6a6ae4e4e7da50..72f58df5677e4d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -6,7 +6,7 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx index cda1fe0844e001..db3e96a4e26502 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../../graphql/types'; @@ -16,7 +15,7 @@ describe('SortIndicator', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the sort indicator', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx index fc77bbd7257048..74fb1e5e4034c8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx @@ -5,7 +5,7 @@ */ import { EuiIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx index d67c6c9648a151..a88062d9093d77 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; import { useMountAppended } from '../../../utils/use_mount_appended'; @@ -36,7 +35,7 @@ describe('DataProviders', () => { show={true} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it should render a placeholder when there are zero data providers', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx index c249e263d1205b..10586657b52a3d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Empty } from './empty'; import { TestProviders } from '../../../mock/test_providers'; @@ -15,7 +14,7 @@ describe('Empty', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); const dropMessage = ['Drop', 'anything', 'highlighted', 'here']; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx index 87d45f6d3db179..a47fb932ed26c7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge, EuiBadgeProps, EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx index cce6dfc1403757..525cc8e301d11c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx @@ -5,7 +5,7 @@ */ import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx index aa3f07cb1b17ab..f0d7ca83fb391e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; @@ -17,7 +16,7 @@ describe('Provider', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the data provider', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx index 05dc7b7b84587d..badc92d00c1746 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { AndOrBadge } from '../../and_or_badge'; import { BrowserFields } from '../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx index 17457b900f3a9d..1a1e8292b7e027 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx @@ -6,7 +6,7 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx index c9454846c5548b..d8076ac90e6b20 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper'; @@ -36,7 +35,7 @@ describe('Providers', () => { onToggleDataProviderExcluded={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the data providers', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx index 4d095485ef69d0..bfe99f6920e663 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiFormHelpText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx index 8f5d91d8ce11fb..1c5df9d220a62b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx index 07b7741e5c1521..b6ca4fe125c696 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; @@ -41,7 +40,7 @@ describe('Footer Timeline Component', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the loading panel at the beginning ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx index 4527e39128f890..5af7aff4f87955 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../graphql/types'; import { mockIndexPattern } from '../../../mock'; @@ -44,7 +43,7 @@ describe('Header', () => { }} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the data providers', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx index 814d25d9c718d5..7e570d613ca5a3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx @@ -5,7 +5,7 @@ */ import { EuiCallOut } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { IIndexPattern } from 'src/plugins/data/public'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx index 0e222f470f0d7e..ae139c24d01768 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx @@ -18,7 +18,7 @@ import { EuiOverlayMask, EuiToolTip, } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import uuid from 'uuid'; import { Note } from '../../../lib/note'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx index bc05204cc47fe2..495b94f8c02e78 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { mockGlobalState, apolloClientObservable } from '../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx index c9b1a3ced6e93a..5db453988cbb8e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx @@ -5,7 +5,7 @@ */ import { EuiSpacer, EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx index b645202ab4c541..45eb7f85c809f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiSuperSelect, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import { esFilters, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx new file mode 100644 index 00000000000000..ac47b352a6276e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx @@ -0,0 +1,276 @@ +/* + * 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 { + EuiHighlight, + EuiInputPopover, + EuiSuperSelect, + EuiSelectable, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTextColor, + EuiFilterButton, + EuiFilterGroup, + EuiSpacer, +} from '@elastic/eui'; +import { Option } from '@elastic/eui/src/components/selectable/types'; +import { isEmpty } from 'lodash/fp'; +import React, { memo, useCallback, useMemo, useState } from 'react'; +import { ListProps } from 'react-virtualized'; +import styled, { createGlobalStyle } from 'styled-components'; + +import { AllTimelinesQuery } from '../../../containers/timeline/all'; +import { getEmptyTagValue } from '../../empty_value'; +import { isUntitled } from '../../../components/open_timeline/helpers'; +import * as i18nTimeline from '../../../components/open_timeline/translations'; +import { SortFieldTimeline, Direction } from '../../../graphql/types'; +import * as i18n from './translations'; + +const SearchTimelineSuperSelectGlobalStyle = createGlobalStyle` + .euiPopover__panel.euiPopover__panel-isOpen.timeline-search-super-select-popover__popoverPanel { + visibility: hidden; + z-index: 0; + } +`; + +const MyEuiHighlight = styled(EuiHighlight)<{ selected: boolean }>` + padding-left: ${({ selected }) => (selected ? '3px' : '0px')}; +`; + +const MyEuiTextColor = styled(EuiTextColor)<{ selected: boolean }>` + padding-left: ${({ selected }) => (selected ? '20px' : '0px')}; +`; + +interface SearchTimelineSuperSelectProps { + isDisabled: boolean; + timelineId: string | null; + timelineTitle: string | null; + onTimelineChange: (timelineTitle: string, timelineId: string | null) => void; +} + +const basicSuperSelectOptions = [ + { + value: '-1', + inputDisplay: i18n.DEFAULT_TIMELINE_TITLE, + }, +]; + +const getBasicSelectableOptions = (timelineId: string) => [ + { + description: i18n.DEFAULT_TIMELINE_DESCRIPTION, + label: i18n.DEFAULT_TIMELINE_TITLE, + id: null, + title: i18n.DEFAULT_TIMELINE_TITLE, + checked: timelineId === '-1' ? 'on' : undefined, + } as Option, +]; + +const ORIGINAL_PAGE_SIZE = 50; +const POPOVER_HEIGHT = 260; +const TIMELINE_ITEM_HEIGHT = 50; +const SearchTimelineSuperSelectComponent: React.FC = ({ + isDisabled, + timelineId, + timelineTitle, + onTimelineChange, +}) => { + const [pageSize, setPageSize] = useState(ORIGINAL_PAGE_SIZE); + const [heightTrigger, setHeightTrigger] = useState(0); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [searchTimelineValue, setSearchTimelineValue] = useState(''); + const [onlyFavorites, setOnlyFavorites] = useState(false); + + const onSearchTimeline = useCallback(val => { + setSearchTimelineValue(val); + }, []); + + const handleClosePopover = useCallback(() => { + setIsPopoverOpen(false); + }, []); + + const handleOpenPopover = useCallback(() => { + setIsPopoverOpen(true); + }, []); + + const handleOnToggleOnlyFavorites = useCallback(() => { + setOnlyFavorites(!onlyFavorites); + }, [onlyFavorites]); + + const renderTimelineOption = useCallback((option, searchValue) => { + return ( + <> + {option.checked === 'on' && } + + {isUntitled(option) ? i18nTimeline.UNTITLED_TIMELINE : option.title} + +
+ + + {option.description != null && option.description.trim().length > 0 + ? option.description + : getEmptyTagValue()} + + + + ); + }, []); + + const handleTimelineChange = useCallback(options => { + const selectedTimeline = options.filter( + (option: { checked: string }) => option.checked === 'on' + ); + if (selectedTimeline != null && selectedTimeline.length > 0 && onTimelineChange != null) { + onTimelineChange( + isEmpty(selectedTimeline[0].title) + ? i18nTimeline.UNTITLED_TIMELINE + : selectedTimeline[0].title, + selectedTimeline[0].id + ); + } + setIsPopoverOpen(false); + }, []); + + const handleOnScroll = useCallback( + ( + totalTimelines: number, + totalCount: number, + { + clientHeight, + scrollHeight, + scrollTop, + }: { + clientHeight: number; + scrollHeight: number; + scrollTop: number; + } + ) => { + if (totalTimelines < totalCount) { + const clientHeightTrigger = clientHeight * 1.2; + if ( + scrollTop > 10 && + scrollHeight - scrollTop < clientHeightTrigger && + scrollHeight > heightTrigger + ) { + setHeightTrigger(scrollHeight); + setPageSize(pageSize + ORIGINAL_PAGE_SIZE); + } + } + }, + [heightTrigger, pageSize] + ); + + const superSelect = useMemo( + () => ( + + ), + [handleOpenPopover, isDisabled, timelineId, timelineTitle] + ); + + return ( + + + {({ timelines, loading, totalCount }) => ( + <> + + + + + {i18nTimeline.ONLY_FAVORITES} + + + + + + + ({ + description: t.description, + label: t.title, + id: t.savedObjectId, + key: `${t.title}-${index}`, + title: t.title, + checked: t.savedObjectId === timelineId ? 'on' : undefined, + } as Option) + ), + ]} + > + {(list, search) => ( + <> + {search} + {list} + + )} + + + )} + + + + ); +}; + +export const SearchTimelineSuperSelect = memo(SearchTimelineSuperSelectComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/translations.ts new file mode 100644 index 00000000000000..bffee407bc9995 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/translations.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const DEFAULT_TIMELINE_TITLE = i18n.translate('xpack.siem.timeline.defaultTimelineTitle', { + defaultMessage: 'Default blank timeline', +}); + +export const DEFAULT_TIMELINE_DESCRIPTION = i18n.translate( + 'xpack.siem.timeline.defaultTimelineDescription', + { + defaultMessage: 'Timeline offered by default when creating new timeline.', + } +); + +export const SEARCH_BOX_TIMELINE_PLACEHOLDER = i18n.translate( + 'xpack.siem.timeline.searchBoxPlaceholder', + { + defaultMessage: 'e.g. timeline name or description', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx index f0f41fc1f674fd..b6fdc1b2973aaf 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx @@ -27,7 +27,7 @@ export const TimelineBodyGlobalStyle = createGlobalStyle` } `; -export const TimelineBody = styled.div.attrs(({ className }) => ({ +export const TimelineBody = styled.div.attrs(({ className = '' }) => ({ className: `siemTimeline__body ${className}`, }))<{ bodyHeight: number }>` height: ${({ bodyHeight }) => `${bodyHeight}px`}; @@ -56,15 +56,14 @@ TimelineBody.displayName = 'TimelineBody'; * EVENTS TABLE */ -export const EventsTable = styled.div.attrs(({ className }) => ({ +export const EventsTable = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable ${className}`, role: 'table', }))``; -EventsTable.displayName = 'EventsTable'; /* EVENTS HEAD */ -export const EventsThead = styled.div.attrs(({ className }) => ({ +export const EventsThead = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thead ${className}`, role: 'rowgroup', }))` @@ -75,7 +74,6 @@ export const EventsThead = styled.div.attrs(({ className }) => ({ top: 0; z-index: ${({ theme }) => theme.eui.euiZLevel1}; `; -EventsThead.displayName = 'EventsThead'; export const EventsTrHeader = styled.div.attrs(({ className }) => ({ className: `siemEventsTable__trHeader ${className}`, @@ -83,9 +81,8 @@ export const EventsTrHeader = styled.div.attrs(({ className }) => ({ }))` display: flex; `; -EventsTrHeader.displayName = 'EventsTrHeader'; -export const EventsThGroupActions = styled.div.attrs(({ className }) => ({ +export const EventsThGroupActions = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thGroupActions ${className}`, }))<{ actionsColumnWidth: number; justifyContent: string }>` display: flex; @@ -93,24 +90,27 @@ export const EventsThGroupActions = styled.div.attrs(({ className }) => ({ justify-content: ${({ justifyContent }) => justifyContent}; min-width: 0; `; -EventsThGroupActions.displayName = 'EventsThGroupActions'; -export const EventsThGroupData = styled.div.attrs(({ className }) => ({ +export const EventsThGroupData = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thGroupData ${className}`, -}))` +}))<{ isDragging?: boolean }>` display: flex; + + > div:hover .siemEventsHeading__handle { + display: ${({ isDragging }) => (isDragging ? 'none' : 'block')}; + opacity: 1; + visibility: visible; + } `; -EventsThGroupData.displayName = 'EventsThGroupData'; -export const EventsTh = styled.div.attrs(({ className }) => ({ +export const EventsTh = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__th ${className}`, role: 'columnheader', -}))<{ isDragging?: boolean; position?: string }>` +}))` align-items: center; display: flex; flex-shrink: 0; min-width: 0; - position: ${({ position }) => position}; .siemEventsTable__thGroupActions &:first-child:last-child { flex: 1; @@ -121,10 +121,18 @@ export const EventsTh = styled.div.attrs(({ className }) => ({ cursor: move; /* Fallback for IE11 */ cursor: grab; } + + > div:focus { + outline: 0; /* disable focus on Resizable element */ + } + + /* don't display Draggable placeholder */ + [data-rbd-placeholder-context-id] { + display: none !important; + } `; -EventsTh.displayName = 'EventsTh'; -export const EventsThContent = styled.div.attrs(({ className }) => ({ +export const EventsThContent = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thContent ${className}`, }))<{ textAlign?: string }>` font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; @@ -135,19 +143,17 @@ export const EventsThContent = styled.div.attrs(({ className }) => ({ text-align: ${({ textAlign }) => textAlign}; width: 100%; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */ `; -EventsThContent.displayName = 'EventsThContent'; /* EVENTS BODY */ -export const EventsTbody = styled.div.attrs(({ className }) => ({ +export const EventsTbody = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__tbody ${className}`, role: 'rowgroup', }))` overflow-x: hidden; `; -EventsTbody.displayName = 'EventsTbody'; -export const EventsTrGroup = styled.div.attrs(({ className }) => ({ +export const EventsTrGroup = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trGroup ${className}`, }))<{ className?: string }>` border-bottom: ${({ theme }) => theme.eui.euiBorderWidthThin} solid @@ -157,17 +163,15 @@ export const EventsTrGroup = styled.div.attrs(({ className }) => ({ background-color: ${({ theme }) => theme.eui.euiTableHoverColor}; } `; -EventsTrGroup.displayName = 'EventsTrGroup'; -export const EventsTrData = styled.div.attrs(({ className }) => ({ +export const EventsTrData = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trData ${className}`, role: 'row', }))` display: flex; `; -EventsTrData.displayName = 'EventsTrData'; -export const EventsTrSupplement = styled.div.attrs(({ className }) => ({ +export const EventsTrSupplement = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trSupplement ${className}`, }))<{ className: string }>` font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; @@ -175,9 +179,8 @@ export const EventsTrSupplement = styled.div.attrs(({ className }) => ({ padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0 ${({ theme }) => theme.eui.paddingSizes.xl}; `; -EventsTrSupplement.displayName = 'EventsTrSupplement'; -export const EventsTdGroupActions = styled.div.attrs(({ className }) => ({ +export const EventsTdGroupActions = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__tdGroupActions ${className}`, }))<{ actionsColumnWidth: number }>` display: flex; @@ -185,16 +188,14 @@ export const EventsTdGroupActions = styled.div.attrs(({ className }) => ({ flex: 0 0 ${({ actionsColumnWidth }) => `${actionsColumnWidth}px`}; min-width: 0; `; -EventsTdGroupActions.displayName = 'EventsTdGroupActions'; -export const EventsTdGroupData = styled.div.attrs(({ className }) => ({ +export const EventsTdGroupData = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__tdGroupData ${className}`, }))` display: flex; `; -EventsTdGroupData.displayName = 'EventsTdGroupData'; -export const EventsTd = styled.div.attrs(({ className }) => ({ +export const EventsTd = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__td ${className}`, role: 'cell', }))` @@ -207,7 +208,6 @@ export const EventsTd = styled.div.attrs(({ className }) => ({ flex: 1; } `; -EventsTd.displayName = 'EventsTd'; export const EventsTdContent = styled.div.attrs(({ className }) => ({ className: `siemEventsTable__tdContent ${className}`, @@ -219,13 +219,12 @@ export const EventsTdContent = styled.div.attrs(({ className }) => ({ text-align: ${({ textAlign }) => textAlign}; width: 100%; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */ `; -EventsTdContent.displayName = 'EventsTdContent'; /** * EVENTS HEADING */ -export const EventsHeading = styled.div.attrs(({ className }) => ({ +export const EventsHeading = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsHeading ${className}`, }))<{ isLoading: boolean }>` align-items: center; @@ -235,9 +234,8 @@ export const EventsHeading = styled.div.attrs(({ className }) => ({ cursor: ${({ isLoading }) => (isLoading ? 'wait' : 'grab')}; } `; -EventsHeading.displayName = 'EventsHeading'; -export const EventsHeadingTitleButton = styled.button.attrs(({ className }) => ({ +export const EventsHeadingTitleButton = styled.button.attrs(({ className = '' }) => ({ className: `siemEventsHeading__title siemEventsHeading__title--aggregatable ${className}`, type: 'button', }))` @@ -260,16 +258,14 @@ export const EventsHeadingTitleButton = styled.button.attrs(({ className }) => ( margin-left: ${({ theme }) => theme.eui.euiSizeXS}; } `; -EventsHeadingTitleButton.displayName = 'EventsHeadingTitleButton'; export const EventsHeadingTitleSpan = styled.span.attrs(({ className }) => ({ className: `siemEventsHeading__title siemEventsHeading__title--notAggregatable ${className}`, }))` min-width: 0; `; -EventsHeadingTitleSpan.displayName = 'EventsHeadingTitleSpan'; -export const EventsHeadingExtra = styled.div.attrs(({ className }) => ({ +export const EventsHeadingExtra = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsHeading__extra ${className}`, }))` margin-left: auto; @@ -285,9 +281,8 @@ export const EventsHeadingExtra = styled.div.attrs(({ className }) => ({ } } `; -EventsHeadingExtra.displayName = 'EventsHeadingExtra'; -export const EventsHeadingHandle = styled.div.attrs(({ className }) => ({ +export const EventsHeadingHandle = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsHeading__handle ${className}`, }))` background-color: ${({ theme }) => theme.eui.euiBorderColor}; @@ -297,17 +292,11 @@ export const EventsHeadingHandle = styled.div.attrs(({ className }) => ({ visibility: hidden; width: ${({ theme }) => theme.eui.euiBorderWidthThick}; - .siemEventsTable__thead:hover & { - opacity: 1; - visibility: visible; - } - &:hover { background-color: ${({ theme }) => theme.eui.euiColorPrimary}; cursor: col-resize; } `; -EventsHeadingHandle.displayName = 'EventsHeadingHandle'; /** * EVENTS LOADING diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx index bb500de239da71..2971053bc5252b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { timelineQuery } from '../../containers/timeline/index.gql_query'; @@ -78,7 +77,7 @@ describe('Timeline', () => { toggleColumn={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the timeline header', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index 5646b26428bf86..e15c58d32425a8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup } from '@elastic/eui'; import { getOr, isEmpty } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx b/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx index 3a7774298f6afa..bfca035e891e8e 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ModalAllErrors } from './modal_all_errors'; import { AppToast } from '.'; @@ -30,7 +29,7 @@ describe('Modal all errors', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders null when isShowing is negative', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx index 8c5a08fdf5e214..92868b23a3ccd8 100644 --- a/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx @@ -5,15 +5,14 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TruncatableText } from '.'; describe('TruncatableText', () => { test('renders correctly against snapshot', () => { const wrapper = shallow({'Hiding in plain sight'}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it adds the hidden overflow style', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx index 63412302fedfb4..67823bea9e170c 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { HookWrapper } from '../../mock'; import { SiemPageName } from '../../pages/home/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx index 705b2106be3157..f673b77ea13c5c 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { HookWrapper } from '../../mock/hook_wrapper'; import { SiemPageName } from '../../pages/home/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx index 5d73e9bcf8e71c..788ea14f4bd22d 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -21,7 +20,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); describe('restrict width', () => { @@ -34,7 +33,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); test('custom max width when restrictWidth is number', () => { @@ -46,7 +45,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); test('custom max width when restrictWidth is string', () => { @@ -58,7 +57,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index a3ee878e305b48..f9611995cdb047 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -23,17 +23,15 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; * Add provided Rule * * @param rule to add - * @param kbnVersion current Kibana Version to use for headers * @param signal to cancel request */ -export const addRule = async ({ rule, kbnVersion, signal }: AddRulesProps): Promise => { +export const addRule = async ({ rule, signal }: AddRulesProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { method: rule.id != null ? 'PUT' : 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify(rule), signal, @@ -49,7 +47,6 @@ export const addRule = async ({ rule, kbnVersion, signal }: AddRulesProps): Prom * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) * @param pagination desired pagination options (e.g. page/perPage) * @param id if specified, will return specific rule if exists - * @param kbnVersion current Kibana Version to use for headers * @param signal to cancel request */ export const fetchRules = async ({ @@ -64,7 +61,6 @@ export const fetchRules = async ({ total: 0, }, id, - kbnVersion, signal, }: FetchRulesProps): Promise => { const queryParams = [ @@ -101,16 +97,14 @@ export const fetchRules = async ({ * Fetch a Rule by providing a Rule ID * * @param id Rule ID's (not rule_id) - * @param kbnVersion current Kibana Version to use for headers */ -export const fetchRuleById = async ({ id, kbnVersion, signal }: FetchRuleProps): Promise => { +export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { method: 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); @@ -124,21 +118,15 @@ export const fetchRuleById = async ({ id, kbnVersion, signal }: FetchRuleProps): * * @param ids array of Rule ID's (not rule_id) to enable/disable * @param enabled to enable or disable - * @param kbnVersion current Kibana Version to use for headers */ -export const enableRules = async ({ - ids, - enabled, - kbnVersion, -}: EnableRulesProps): Promise => { +export const enableRules = async ({ ids, enabled }: EnableRulesProps): Promise => { const requests = ids.map(id => fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { method: 'PUT', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify({ id, enabled }), }) @@ -155,9 +143,8 @@ export const enableRules = async ({ * Deletes provided Rule ID's * * @param ids array of Rule ID's (not rule_id) to delete - * @param kbnVersion current Kibana Version to use for headers */ -export const deleteRules = async ({ ids, kbnVersion }: DeleteRulesProps): Promise => { +export const deleteRules = async ({ ids }: DeleteRulesProps): Promise => { // TODO: Don't delete if immutable! const requests = ids.map(id => fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { @@ -165,8 +152,7 @@ export const deleteRules = async ({ ids, kbnVersion }: DeleteRulesProps): Promis credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, }) ); @@ -182,20 +168,15 @@ export const deleteRules = async ({ ids, kbnVersion }: DeleteRulesProps): Promis * Duplicates provided Rules * * @param rule to duplicate - * @param kbnVersion current Kibana Version to use for headers */ -export const duplicateRules = async ({ - rules, - kbnVersion, -}: DuplicateRulesProps): Promise => { +export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise => { const requests = rules.map(rule => fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify({ ...rule, diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx index 82490991236de5..ea03c34ec31ba8 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx @@ -6,8 +6,6 @@ import { useEffect, useState, Dispatch } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { useStateToaster } from '../../../components/toasters'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; @@ -26,7 +24,6 @@ export const usePersistRule = (): Return => { const [rule, setRule] = useState(null); const [isSaved, setIsSaved] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -37,7 +34,7 @@ export const usePersistRule = (): Return => { if (rule != null) { try { setIsLoading(true); - await persistRule({ rule, kbnVersion, signal: abortCtrl.signal }); + await persistRule({ rule, signal: abortCtrl.signal }); if (isSubscribed) { setIsSaved(true); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index 0885e541cead5d..655299c4a2a34b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -41,7 +41,6 @@ export type NewRule = t.TypeOf; export interface AddRulesProps { rule: NewRule; - kbnVersion: string; signal: AbortSignal; } @@ -71,8 +70,8 @@ export const RuleSchema = t.intersection([ risk_score: t.number, rule_id: t.string, severity: t.string, - type: t.string, tags: t.array(t.string), + type: t.string, to: t.string, threats: t.array(t.unknown), updated_at: t.string, @@ -80,6 +79,8 @@ export const RuleSchema = t.intersection([ }), t.partial({ saved_id: t.string, + timeline_id: t.string, + timeline_title: t.string, }), ]); @@ -98,7 +99,6 @@ export interface FetchRulesProps { pagination?: PaginationOptions; filterOptions?: FilterOptions; id?: string; - kbnVersion: string; signal: AbortSignal; } @@ -117,22 +117,18 @@ export interface FetchRulesResponse { export interface FetchRuleProps { id: string; - kbnVersion: string; signal: AbortSignal; } export interface EnableRulesProps { ids: string[]; enabled: boolean; - kbnVersion: string; } export interface DeleteRulesProps { ids: string[]; - kbnVersion: string; } export interface DuplicateRulesProps { rules: Rules; - kbnVersion: string; } diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx index ad0b87385ee790..22ba86cd09f743 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx @@ -6,8 +6,6 @@ import { useEffect, useState } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { useStateToaster } from '../../../components/toasters'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import { fetchRuleById } from './api'; @@ -25,7 +23,6 @@ type Return = [boolean, Rule | null]; export const useRule = (id: string | undefined): Return => { const [rule, setRule] = useState(null); const [loading, setLoading] = useState(true); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -37,7 +34,6 @@ export const useRule = (id: string | undefined): Return => { setLoading(true); const ruleResponse = await fetchRuleById({ id: idToFetch, - kbnVersion, signal: abortCtrl.signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index 66285c804aa283..b49dd8d51d4f75 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -6,8 +6,6 @@ import { useEffect, useState } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; import { useStateToaster } from '../../../components/toasters'; import { fetchRules } from './api'; @@ -35,7 +33,6 @@ export const useRules = ( data: [], }); const [loading, setLoading] = useState(true); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -48,7 +45,6 @@ export const useRules = ( const fetchRulesResult = await fetchRules({ filterOptions, pagination, - kbnVersion, signal: abortCtrl.signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts index e69bbfe1925fbb..e7641fd37678e2 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts @@ -29,12 +29,10 @@ import { parseJsonFromBody } from '../../../utils/api'; * Fetch Signals by providing a query * * @param query String to match a dsl - * @param kbnVersion current Kibana Version to use for headers * @param signal AbortSignal for cancelling request */ export const fetchQuerySignals = async ({ query, - kbnVersion, signal, }: QuerySignals): Promise> => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_QUERY_SIGNALS_URL}`, { @@ -42,8 +40,7 @@ export const fetchQuerySignals = async ({ credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: query, signal, @@ -58,13 +55,11 @@ export const fetchQuerySignals = async ({ * * @param query of signals to update * @param status to update to('open' / 'closed') - * @param kbnVersion current Kibana Version to use for headers * @param signal AbortSignal for cancelling request */ export const updateSignalStatus = async ({ query, status, - kbnVersion, signal, }: UpdateSignalStatusProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_SIGNALS_STATUS_URL}`, { @@ -72,8 +67,7 @@ export const updateSignalStatus = async ({ credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify({ status, ...query }), signal, @@ -86,20 +80,15 @@ export const updateSignalStatus = async ({ /** * Fetch Signal Index * - * @param kbnVersion current Kibana Version to use for headers * @param signal AbortSignal for cancelling request */ -export const getSignalIndex = async ({ - kbnVersion, - signal, -}: BasicSignals): Promise => { +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { method: 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); @@ -117,20 +106,15 @@ export const getSignalIndex = async ({ /** * Get User Privileges * - * @param kbnVersion current Kibana Version to use for headers * @param signal AbortSignal for cancelling request */ -export const getUserPrivilege = async ({ - kbnVersion, - signal, -}: BasicSignals): Promise => { +export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PRIVILEGES_URL}`, { method: 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); @@ -142,20 +126,15 @@ export const getUserPrivilege = async ({ /** * Create Signal Index if needed it * - * @param kbnVersion current Kibana Version to use for headers * @param signal AbortSignal for cancelling request */ -export const createSignalIndex = async ({ - kbnVersion, - signal, -}: BasicSignals): Promise => { +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts index 118c2b367ca5b7..32f53691bae874 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts @@ -7,7 +7,6 @@ export * from './errors_types'; export interface BasicSignals { - kbnVersion: string; signal: AbortSignal; } export interface QuerySignals extends BasicSignals { @@ -39,7 +38,6 @@ export interface SignalSearchResponse extend export interface UpdateSignalStatusProps { query: object; status: 'open' | 'closed'; - kbnVersion: string; signal?: AbortSignal; // TODO: implement cancelling } diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx index 6f897703059f74..aa66df53d9fd9f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -6,8 +6,6 @@ import { useEffect, useState } from 'react'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; -import { useUiSetting$ } from '../../../lib/kibana'; import { getUserPrivilege } from './api'; type Return = [boolean, boolean | null, boolean | null]; @@ -20,7 +18,6 @@ export const usePrivilegeUser = (): Return => { const [loading, setLoading] = useState(true); const [isAuthenticated, setAuthenticated] = useState(null); const [hasWrite, setHasWrite] = useState(null); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; @@ -30,7 +27,6 @@ export const usePrivilegeUser = (): Return => { async function fetchData() { try { const privilege = await getUserPrivilege({ - kbnVersion, signal: abortCtrl.signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx index 9501f1189a4839..65a5ac866e68d7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx @@ -6,9 +6,6 @@ import { useEffect, useState } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; - import { fetchQuerySignals } from './api'; import { SignalSearchResponse } from './types'; @@ -23,7 +20,6 @@ type Return = [boolean, SignalSearchResponse | null]; export const useQuerySignals = (query: string): Return => { const [signals, setSignals] = useState | null>(null); const [loading, setLoading] = useState(true); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; @@ -34,7 +30,6 @@ export const useQuerySignals = (query: string): Return => try { const signalResponse = await fetchQuerySignals({ query, - kbnVersion, signal: abortCtrl.signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx index 347c90fa3b411d..1ff4422cf6411d 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx @@ -6,10 +6,8 @@ import { useEffect, useState, useRef } from 'react'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import { useStateToaster } from '../../../components/toasters'; -import { useUiSetting$ } from '../../../lib/kibana'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; import { PostSignalError } from './types'; @@ -28,7 +26,6 @@ export const useSignalIndex = (): Return => { const [signalIndexName, setSignalIndexName] = useState(null); const [signalIndexExists, setSignalIndexExists] = useState(null); const createDeSignalIndex = useRef(null); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -38,10 +35,7 @@ export const useSignalIndex = (): Return => { const fetchData = async () => { try { setLoading(true); - const signal = await getSignalIndex({ - kbnVersion, - signal: abortCtrl.signal, - }); + const signal = await getSignalIndex({ signal: abortCtrl.signal }); if (isSubscribed && signal != null) { setSignalIndexName(signal.name); @@ -62,10 +56,7 @@ export const useSignalIndex = (): Return => { let isFetchingData = false; try { setLoading(true); - await createSignalIndex({ - kbnVersion, - signal: abortCtrl.signal, - }); + await createSignalIndex({ signal: abortCtrl.signal }); if (isSubscribed) { isFetchingData = true; diff --git a/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx b/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx index 12282241247cbc..8d319ffe239027 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx @@ -17,13 +17,9 @@ const emptyIndexPattern: IndexPatternSavedObject[] = []; * * TODO: Refactor to context provider: https://github.com/elastic/siem-team/issues/448 * - * @param headers * @param signal */ -export const getIndexPatterns = async ( - signal: AbortSignal, - kbnVersion: string -): Promise => { +export const getIndexPatterns = async (signal: AbortSignal): Promise => { const response = await fetch( `${chrome.getBasePath()}/api/saved_objects/_find?type=index-pattern&fields=title&fields=type&per_page=10000`, { @@ -31,9 +27,8 @@ export const getIndexPatterns = async ( credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-xsrf': kbnVersion, - 'kbn-version': kbnVersion, 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, } diff --git a/x-pack/legacy/plugins/siem/public/app.ts b/x-pack/legacy/plugins/siem/public/hooks/index.ts similarity index 79% rename from x-pack/legacy/plugins/siem/public/app.ts rename to x-pack/legacy/plugins/siem/public/hooks/index.ts index b068f8a9becda5..5049e4587d3837 100644 --- a/x-pack/legacy/plugins/siem/public/app.ts +++ b/x-pack/legacy/plugins/siem/public/hooks/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './apps/index'; +export { useDateFormat, useTimeZone } from './use_ui_settings'; diff --git a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx index f5b595b0d01c61..7abe88402096c8 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx @@ -6,11 +6,9 @@ import { useEffect, useState } from 'react'; -import { DEFAULT_KBN_VERSION } from '../../common/constants'; import { useStateToaster } from '../components/toasters'; import { errorToToaster } from '../components/ml/api/error_to_toaster'; import { IndexPatternSavedObject } from '../components/ml_popover/types'; -import { useUiSetting$ } from '../lib/kibana'; import { getIndexPatterns } from './api/api'; import * as i18n from './translations'; @@ -21,7 +19,6 @@ export const useIndexPatterns = (refreshToggle = false): Return => { const [indexPatterns, setIndexPatterns] = useState([]); const [isLoading, setIsLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; @@ -30,7 +27,7 @@ export const useIndexPatterns = (refreshToggle = false): Return => { async function fetchIndexPatterns() { try { - const data = await getIndexPatterns(abortCtrl.signal, kbnVersion); + const data = await getIndexPatterns(abortCtrl.signal); if (isSubscribed) { setIndexPatterns(data); diff --git a/x-pack/legacy/plugins/siem/public/hooks/use_ui_settings.ts b/x-pack/legacy/plugins/siem/public/hooks/use_ui_settings.ts new file mode 100644 index 00000000000000..7eb0242e8e1160 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/hooks/use_ui_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 moment from 'moment-timezone'; + +import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../common/constants'; +import { useUiSetting } from '../lib/kibana'; + +export const useDateFormat = (): string => useUiSetting(DEFAULT_DATE_FORMAT); + +export const useTimeZone = (): string => { + const timeZone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); + return timeZone === 'Browser' ? moment.tz.guess() : timeZone; +}; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts b/x-pack/legacy/plugins/siem/public/index.ts similarity index 57% rename from x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts rename to x-pack/legacy/plugins/siem/public/index.ts index 3edb289985d55c..3a396a0637ea12 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts +++ b/x-pack/legacy/plugins/siem/public/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as logPositionActions from './actions'; -import * as logPositionSelectors from './selectors'; +import { Plugin, PluginInitializerContext } from './plugin'; -export { logPositionActions, logPositionSelectors }; -export * from './reducer'; +export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context); diff --git a/x-pack/legacy/plugins/siem/public/legacy.ts b/x-pack/legacy/plugins/siem/public/legacy.ts new file mode 100644 index 00000000000000..49a03c93120d4a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/legacy.ts @@ -0,0 +1,15 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; + +import { PluginInitializerContext } from '../../../../../src/core/public'; +import { plugin } from './'; + +const pluginInstance = plugin({} as PluginInitializerContext); + +pluginInstance.setup(npSetup.core, npSetup.plugins); +pluginInstance.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx b/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx index 44cf7502f3c5ec..fdb6ed130a5256 100644 --- a/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx @@ -6,7 +6,7 @@ import { EuiGlobalToastListToast as Toast, EuiButtonIcon } from '@elastic/eui'; import copy from 'copy-to-clipboard'; -import * as React from 'react'; +import React from 'react'; import uuid from 'uuid'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx b/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx index c392c2511551f6..ee94c2daa3fc46 100644 --- a/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Clipboard } from './clipboard'; diff --git a/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts b/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts index 80a42b91dbd32a..9cdd4148134ad2 100644 --- a/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts @@ -20,9 +20,7 @@ export const getLinks = (cache: InMemoryCache) => [ }), new HttpLink({ credentials: 'same-origin', - headers: { - 'kbn-xsrf': chrome.getXsrfToken(), - }, + headers: { 'kbn-xsrf': 'true' }, uri: `${chrome.getBasePath()}/api/siem/graphql`, }), ]; diff --git a/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts b/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts index 96d9c8330d265d..012a1cfef5da2d 100644 --- a/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts +++ b/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts @@ -12,7 +12,7 @@ import { useUiSetting$, withKibana, } from '../../../../../../../src/plugins/kibana_react/public'; -import { StartServices } from '../../apps/plugin'; +import { StartServices } from '../../plugin'; export type KibanaContext = KibanaReactContextValue; export interface WithKibanaProps { diff --git a/x-pack/legacy/plugins/siem/public/lib/lib.ts b/x-pack/legacy/plugins/siem/public/lib/lib.ts index 7a6cd32aa8864c..e7b39d2ea50f95 100644 --- a/x-pack/legacy/plugins/siem/public/lib/lib.ts +++ b/x-pack/legacy/plugins/siem/public/lib/lib.ts @@ -24,7 +24,6 @@ export interface AppFrameworkAdapter { darkMode?: boolean; indexPattern?: string; anomalyScore?: number; - kbnVersion?: string; scaledDateFormat?: string; timezone?: string; diff --git a/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx b/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx index 4f1c2049c00271..292ddc036dcafd 100644 --- a/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; interface HookWrapperProps { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/legacy/plugins/siem/public/mock/index.ts b/x-pack/legacy/plugins/siem/public/mock/index.ts index 620e266618c5c9..dbf5f2e55e7134 100644 --- a/x-pack/legacy/plugins/siem/public/mock/index.ts +++ b/x-pack/legacy/plugins/siem/public/mock/index.ts @@ -8,7 +8,6 @@ export * from './global_state'; export * from './header'; export * from './hook_wrapper'; export * from './index_pattern'; -export * from './kibana_config'; export * from './mock_timeline_data'; export * from './mock_detail_item'; export * from './netflow'; diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_config.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_config.ts deleted file mode 100644 index 23f1f0e86dd6aa..00000000000000 --- a/x-pack/legacy/plugins/siem/public/mock/kibana_config.ts +++ /dev/null @@ -1,123 +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 { - DEFAULT_DATE_FORMAT, - DEFAULT_DATE_FORMAT_TZ, - DEFAULT_BYTES_FORMAT, - DEFAULT_KBN_VERSION, - DEFAULT_TIMEZONE_BROWSER, - DEFAULT_TIMEPICKER_QUICK_RANGES, -} from '../../common/constants'; - -export interface MockFrameworks { - bytesFormat: string; - dateFormat: string; - dateFormatTz: string; - timezone: string; -} - -export const getMockKibanaUiSetting = (config: MockFrameworks) => (key: string) => { - if (key === DEFAULT_DATE_FORMAT) { - return [config.dateFormat]; - } else if (key === DEFAULT_DATE_FORMAT_TZ) { - return [config.dateFormatTz]; - } else if (key === DEFAULT_BYTES_FORMAT) { - return [config.bytesFormat]; - } else if (key === DEFAULT_KBN_VERSION) { - return ['8.0.0']; - } else if (key === DEFAULT_TIMEZONE_BROWSER) { - return config && config.timezone ? [config.timezone] : ['America/New_York']; - } else if (key === DEFAULT_TIMEPICKER_QUICK_RANGES) { - return [ - [ - { - from: 'now/d', - to: 'now/d', - display: 'Today', - }, - { - from: 'now/w', - to: 'now/w', - display: 'This week', - }, - { - from: 'now-15m', - to: 'now', - display: 'Last 15 minutes', - }, - { - from: 'now-30m', - to: 'now', - display: 'Last 30 minutes', - }, - { - from: 'now-1h', - to: 'now', - display: 'Last 1 hour', - }, - { - from: 'now-24h', - to: 'now', - display: 'Last 24 hours', - }, - { - from: 'now-7d', - to: 'now', - display: 'Last 7 days', - }, - { - from: 'now-30d', - to: 'now', - display: 'Last 30 days', - }, - { - from: 'now-90d', - to: 'now', - display: 'Last 90 days', - }, - { - from: 'now-1y', - to: 'now', - display: 'Last 1 year', - }, - ], - ]; - } - return [null]; -}; - -export const mockFrameworks: Readonly> = { - bytes_short: { - bytesFormat: '0b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'Browser', - timezone: 'America/Denver', - }, - default_browser: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'Browser', - timezone: 'America/Denver', - }, - default_ET: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'America/New_York', - timezone: 'America/New_York', - }, - default_MT: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'America/Denver', - timezone: 'America/Denver', - }, - default_UTC: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'UTC', - timezone: 'UTC', - }, -}; diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts index a6b4fe664efba0..7d843977d1f322 100644 --- a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts +++ b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts @@ -22,7 +22,7 @@ import { DEFAULT_TO, DEFAULT_INTERVAL_PAUSE, DEFAULT_INTERVAL_VALUE, - DEFAULT_TIMEZONE_BROWSER, + DEFAULT_BYTES_FORMAT, } from '../../common/constants'; import { defaultIndexPattern } from '../../default_index_pattern'; import { createKibanaCoreStartMock, createKibanaPluginsStartMock } from './kibana_core'; @@ -40,8 +40,8 @@ export const mockUiSettings: Record = { value: DEFAULT_INTERVAL_VALUE, }, [DEFAULT_INDEX_KEY]: defaultIndexPattern, + [DEFAULT_BYTES_FORMAT]: '0,0.[0]b', [DEFAULT_DATE_FORMAT_TZ]: 'UTC', - [DEFAULT_TIMEZONE_BROWSER]: 'America/New_York', [DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS', [DEFAULT_DARK_MODE]: false, }; @@ -87,11 +87,15 @@ export const createWithKibanaMock = () => { export const createKibanaContextProviderMock = () => { const kibana = createUseKibanaMock()(); + const uiSettings = { + ...kibana.services.uiSettings, + get: createUseUiSettingMock(), + }; // eslint-disable-next-line @typescript-eslint/no-explicit-any return ({ services, ...rest }: any) => React.createElement(KibanaContextProvider, { ...rest, - services: { ...kibana.services, ...services }, + services: { ...kibana.services, uiSettings, ...services }, }); }; diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index f0f719a60a7088..c7692755c13303 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -9,7 +9,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { InMemoryCache as Cache } from 'apollo-cache-inmemory'; import ApolloClient from 'apollo-client'; import { ApolloLink } from 'apollo-link'; -import * as React from 'react'; +import React from 'react'; import { ApolloProvider } from 'react-apollo'; import { DragDropContext, DropResult, ResponderProvided } from 'react-beautiful-dnd'; import { Provider as ReduxStoreProvider } from 'react-redux'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx index c0ed58daeca7fc..d08e282a4c399b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx @@ -42,18 +42,13 @@ export const updateSignalStatusAction = async ({ status, setEventsLoading, setEventsDeleted, - kbnVersion, }: UpdateSignalStatusActionProps) => { try { setEventsLoading({ eventIds: signalIds, isLoading: true }); const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); - await updateSignalStatus({ - query: queryObject, - status, - kbnVersion, - }); + await updateSignalStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: signalIds, isDeleted: true }); } catch (e) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx index 6b8cd5521c5337..1a7ad5822a2468 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx @@ -172,13 +172,11 @@ export const getSignalsActions = ({ setEventsDeleted, createTimeline, status, - kbnVersion, }: { setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; createTimeline: CreateTimeline; status: 'open' | 'closed'; - kbnVersion: string; }): TimelineAction[] => [ { getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( @@ -211,7 +209,6 @@ export const getSignalsActions = ({ status, setEventsLoading, setEventsDeleted, - kbnVersion, }) } iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index f9e80334a88820..47a78482cfb6e9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -25,8 +25,7 @@ import { SignalFilterOption, SignalsTableFilterGroup, } from './signals_filter_group'; -import { useKibana, useUiSetting$ } from '../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../../common/constants'; +import { useKibana } from '../../../../lib/kibana'; import { defaultHeaders } from '../../../../components/timeline/body/column_headers/default_headers'; import { ColumnHeader } from '../../../../components/timeline/body/column_headers/column_header'; import { esFilters, esQuery } from '../../../../../../../../../src/plugins/data/common/es_query'; @@ -121,7 +120,6 @@ export const SignalsTableComponent = React.memo( const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([signalsIndex]); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const kibana = useKibana(); const getGlobalQuery = useCallback(() => { @@ -208,7 +206,6 @@ export const SignalsTableComponent = React.memo( status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, - kbnVersion, }); }, [ @@ -261,9 +258,8 @@ export const SignalsTableComponent = React.memo( setEventsLoading: setEventsLoadingCallback, setEventsDeleted: setEventsDeletedCallback, status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, - kbnVersion, }), - [createTimelineCallback, filterGroup, kbnVersion] + [createTimelineCallback, filterGroup] ); const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts index b02b8eb0ef976e..51bea27ec6a4b7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts @@ -31,7 +31,6 @@ export interface UpdateSignalStatusActionProps { status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; - kbnVersion: string; } export type SendSignalsToTimeline = () => void; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index e8a2c98a94a562..9c95c74cd62a54 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -56,13 +56,13 @@ export const DetectionEngineContainer = React.memo(() => { - + - + - + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index 4de6136e9d3ded..469745262d9446 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -17,18 +17,14 @@ import { import { Action } from './reducer'; export const editRuleAction = (rule: Rule, history: H.History) => { - history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/${rule.id}/edit`); + history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`); }; export const runRuleAction = () => {}; -export const duplicateRuleAction = async ( - rule: Rule, - dispatch: React.Dispatch, - kbnVersion: string -) => { +export const duplicateRuleAction = async (rule: Rule, dispatch: React.Dispatch) => { dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true }); - const duplicatedRule = await duplicateRules({ rules: [rule], kbnVersion }); + const duplicatedRule = await duplicateRules({ rules: [rule] }); dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false }); dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id }); }; @@ -37,25 +33,20 @@ export const exportRulesAction = async (rules: Rule[], dispatch: React.Dispatch< dispatch({ type: 'setExportPayload', exportPayload: rules }); }; -export const deleteRulesAction = async ( - ids: string[], - dispatch: React.Dispatch, - kbnVersion: string -) => { +export const deleteRulesAction = async (ids: string[], dispatch: React.Dispatch) => { dispatch({ type: 'updateLoading', ids, isLoading: true }); - const deletedRules = await deleteRules({ ids, kbnVersion }); + const deletedRules = await deleteRules({ ids }); dispatch({ type: 'deleteRules', rules: deletedRules }); }; export const enableRulesAction = async ( ids: string[], enabled: boolean, - dispatch: React.Dispatch, - kbnVersion: string + dispatch: React.Dispatch ) => { try { dispatch({ type: 'updateLoading', ids, isLoading: true }); - const updatedRules = await enableRules({ ids, enabled, kbnVersion }); + const updatedRules = await enableRules({ ids, enabled }); dispatch({ type: 'updateRules', rules: updatedRules }); } catch { // TODO Add error toast support to actions (and @throw jsdoc to api calls) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index c8fb9d98fde6ae..72d38454ad9bc3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -14,8 +14,7 @@ import { deleteRulesAction, enableRulesAction, exportRulesAction } from './actio export const getBatchItems = ( selectedState: TableData[], dispatch: React.Dispatch, - closePopover: () => void, - kbnVersion: string + closePopover: () => void ) => { const containsEnabled = selectedState.some(v => v.activate); const containsDisabled = selectedState.some(v => !v.activate); @@ -29,7 +28,7 @@ export const getBatchItems = ( onClick={async () => { closePopover(); const deactivatedIds = selectedState.filter(s => !s.activate).map(s => s.id); - await enableRulesAction(deactivatedIds, true, dispatch, kbnVersion); + await enableRulesAction(deactivatedIds, true, dispatch); }} > {i18n.BATCH_ACTION_ACTIVATE_SELECTED} @@ -41,7 +40,7 @@ export const getBatchItems = ( onClick={async () => { closePopover(); const activatedIds = selectedState.filter(s => s.activate).map(s => s.id); - await enableRulesAction(activatedIds, false, dispatch, kbnVersion); + await enableRulesAction(activatedIds, false, dispatch); }} > {i18n.BATCH_ACTION_DEACTIVATE_SELECTED} @@ -78,8 +77,7 @@ export const getBatchItems = ( closePopover(); await deleteRulesAction( selectedState.map(({ sourceRule: { id } }) => id), - dispatch, - kbnVersion + dispatch ); }} > diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 1fcd8c7da82569..42c4bb1d0ef95d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -33,7 +33,7 @@ import * as i18n from '../translations'; import { PreferenceFormattedDate } from '../../../../components/formatted_date'; import { RuleSwitch } from '../components/rule_switch'; -const getActions = (dispatch: React.Dispatch, kbnVersion: string, history: H.History) => [ +const getActions = (dispatch: React.Dispatch, history: H.History) => [ { description: i18n.EDIT_RULE_SETTINGS, icon: 'visControls', @@ -52,7 +52,7 @@ const getActions = (dispatch: React.Dispatch, kbnVersion: string, histor description: i18n.DUPLICATE_RULE, icon: 'copy', name: i18n.DUPLICATE_RULE, - onClick: (rowItem: TableData) => duplicateRuleAction(rowItem.sourceRule, dispatch, kbnVersion), + onClick: (rowItem: TableData) => duplicateRuleAction(rowItem.sourceRule, dispatch), }, { description: i18n.EXPORT_RULE, @@ -64,14 +64,13 @@ const getActions = (dispatch: React.Dispatch, kbnVersion: string, histor description: i18n.DELETE_RULE, icon: 'trash', name: i18n.DELETE_RULE, - onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, kbnVersion), + onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch), }, ]; // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? export const getColumns = ( dispatch: React.Dispatch, - kbnVersion: string, history: H.History ): Array | EuiTableActionsColumnType> => [ { @@ -173,7 +172,7 @@ export const getColumns = ( width: '85px', }, { - actions: getActions(dispatch, kbnVersion, history), + actions: getActions(dispatch, history), width: '40px', } as EuiTableActionsColumnType, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index 1909b75a85835e..f5d39553142429 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -13,7 +13,7 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] id: rule.id, rule_id: rule.rule_id, rule: { - href: `#/detection-engine/rules/${encodeURIComponent(rule.id)}`, + href: `#/detection-engine/rules/id/${encodeURIComponent(rule.id)}`, name: rule.name, status: 'Status Placeholder', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 442360bbf14846..060f8baccc3b73 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -31,8 +31,6 @@ import { getBatchItems } from './batch_actions'; import { EuiBasicTableOnChange, TableData } from '../types'; import { allRulesReducer, State } from './reducer'; import * as i18n from '../translations'; -import { useUiSetting$ } from '../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../../common/constants'; import { JSONDownloader } from '../components/json_downloader'; import { useStateToaster } from '../../../../components/toasters'; @@ -78,16 +76,13 @@ export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importComp const history = useHistory(); const [isInitialLoad, setIsInitialLoad] = useState(true); const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); const getBatchItemsPopoverContent = useCallback( (closePopover: () => void) => ( - + ), - [selectedItems, dispatch, kbnVersion] + [selectedItems, dispatch] ); useEffect(() => { @@ -185,7 +180,7 @@ export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importComp boolean; } -export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { +const MyEuiFormRow = styled(EuiFormRow)` + .euiFormRow__labelWrapper { + .euiText { + padding-right: 32px; + } + } +`; + +export const AddItem = ({ + addText, + dataTestSubj, + field, + idAria, + isDisabled, + validate, +}: AddItemProps) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - // const [items, setItems] = useState(['']); const [haveBeenKeyboardDeleted, setHaveBeenKeyboardDeleted] = useState(-1); const inputsRef = useRef([]); @@ -104,7 +128,7 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad const values = field.value as string[]; return ( - - + + updateItem(e, index)} fullWidth {...euiFieldProps} /> + + removeItem(index)} aria-label={RuleI18n.DELETE} /> - } - onChange={e => updateItem(e, index)} - compressed - fullWidth - {...euiFieldProps} - /> + + + {values.length - 1 !== index && }
); })} - + {addText} - + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx new file mode 100644 index 00000000000000..09d0c1131ea10d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx @@ -0,0 +1,222 @@ +/* + * 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 { + EuiBadge, + EuiLoadingSpinner, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiLink, + EuiText, + EuiListGroup, +} from '@elastic/eui'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +import { isEmpty } from 'lodash/fp'; +import React from 'react'; +import styled from 'styled-components'; + +import { esFilters } from '../../../../../../../../../../src/plugins/data/public'; + +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; + +import { FilterLabel } from './filter_label'; +import * as i18n from './translations'; +import { BuildQueryBarDescription, BuildThreatsDescription, ListItems } from './types'; + +const EuiBadgeWrap = styled(EuiBadge)` + .euiBadge__text { + white-space: pre-wrap !important; + } +`; + +export const buildQueryBarDescription = ({ + field, + filters, + filterManager, + query, + savedId, + indexPatterns, +}: BuildQueryBarDescription): ListItems[] => { + let items: ListItems[] = []; + if (!isEmpty(filters)) { + filterManager.setFilters(filters); + items = [ + ...items, + { + title: <>{i18n.FILTERS_LABEL} , + description: ( + + {filterManager.getFilters().map((filter, index) => ( + + + {indexPatterns != null ? ( + + ) : ( + + )} + + + ))} + + ), + }, + ]; + } + if (!isEmpty(query.query)) { + items = [ + ...items, + { + title: <>{i18n.QUERY_LABEL} , + description: <>{query.query} , + }, + ]; + } + if (!isEmpty(savedId)) { + items = [ + ...items, + { + title: <>{i18n.SAVED_ID_LABEL} , + description: <>{savedId} , + }, + ]; + } + return items; +}; + +const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` + .euiFlexItem { + margin-bottom: 0px; + } +`; + +const MyEuiListGroup = styled(EuiListGroup)` + padding: 0px; + .euiListGroupItem__button { + padding: 0px; + } +`; + +export const buildThreatsDescription = ({ + label, + threats, +}: BuildThreatsDescription): ListItems[] => { + if (threats.length > 0) { + return [ + { + title: label, + description: ( + + {threats.map((threat, index) => { + const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); + return ( + + +
+ + {tactic != null ? tactic.text : ''} + +
+ { + const myTechnique = techniquesOptions.find(t => t.name === technique.name); + return { + label: myTechnique != null ? myTechnique.label : '', + href: technique.reference, + target: '_blank', + }; + })} + /> +
+
+ ); + })} +
+ ), + }, + ]; + } + return []; +}; + +export const buildStringArrayDescription = ( + label: string, + field: string, + values: string[] +): ListItems[] => { + if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { + return [ + { + title: label, + description: ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + {val} + + ) + )} + + ), + }, + ]; + } + return []; +}; + +export const buildSeverityDescription = (label: string, value: string): ListItems[] => { + return [ + { + title: label, + description: ( + + {value} + + ), + }, + ]; +}; + +export const buildUrlsDescription = (label: string, values: string[]): ListItems[] => { + if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { + return [ + { + title: label, + description: ( + ({ + label: val, + href: val, + iconType: 'link', + size: 'xs', + target: '_blank', + }))} + /> + ), + }, + ]; + } + return []; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx index a05f43579e6694..198756fc2336bc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx @@ -4,19 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBadge, - EuiDescriptionList, - EuiLoadingSpinner, - EuiFlexGroup, - EuiFlexItem, - EuiTextArea, - EuiLink, - EuiText, - EuiListGroup, -} from '@elastic/eui'; +import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiTextArea } from '@elastic/eui'; import { isEmpty, chunk, get, pick } from 'lodash/fp'; -import React, { memo, ReactNode, useState } from 'react'; +import React, { memo, useState } from 'react'; import styled from 'styled-components'; import { @@ -25,13 +15,19 @@ import { FilterManager, Query, } from '../../../../../../../../../../src/plugins/data/public'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/search_super_select/translations'; import { useKibana } from '../../../../../lib/kibana'; -import { FilterLabel } from './filter_label'; -import { FormSchema } from '../shared_imports'; -import * as I18n from './translations'; - import { IMitreEnterpriseAttack } from '../../types'; -import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; +import { FieldValueTimeline } from '../pick_timeline'; +import { FormSchema } from '../shared_imports'; +import { ListItems } from './types'; +import { + buildQueryBarDescription, + buildSeverityDescription, + buildStringArrayDescription, + buildThreatsDescription, + buildUrlsDescription, +} from './helpers'; interface StepRuleDescriptionProps { direction?: 'row' | 'column'; @@ -40,29 +36,10 @@ interface StepRuleDescriptionProps { schema: FormSchema; } -const EuiBadgeWrap = styled(EuiBadge)` - .euiBadge__text { - white-space: pre-wrap !important; - } -`; - const EuiFlexItemWidth = styled(EuiFlexItem)<{ direction: string }>` ${props => (props.direction === 'row' ? 'width : 50%;' : 'width: 100%;')}; `; -const MyEuiListGroup = styled(EuiListGroup)` - padding: 0px; - .euiListGroupItem__button { - padding: 0px; - } -`; - -const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` - .euiFlexItem { - margin-bottom: 0px; - } -`; - const MyEuiTextArea = styled(EuiTextArea)` max-width: 100%; height: 80px; @@ -87,9 +64,9 @@ const StepRuleDescriptionComponent: React.FC = ({ ); return ( - {chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => ( + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => ( - + ))} @@ -98,11 +75,6 @@ const StepRuleDescriptionComponent: React.FC = ({ export const StepRuleDescription = memo(StepRuleDescriptionComponent); -interface ListItems { - title: NonNullable; - description: NonNullable; -} - const buildListItems = ( data: unknown, schema: FormSchema, @@ -130,103 +102,23 @@ const getDescriptionItem = ( filterManager: FilterManager, indexPatterns?: IIndexPattern ): ListItems[] => { - if (field === 'useIndicesConfig') { - return []; - } else if (field === 'queryBar') { + if (field === 'queryBar') { const filters = get('queryBar.filters', value) as esFilters.Filter[]; const query = get('queryBar.query', value) as Query; const savedId = get('queryBar.saved_id', value); - let items: ListItems[] = []; - if (!isEmpty(filters)) { - filterManager.setFilters(filters); - items = [ - ...items, - { - title: <>{I18n.FILTERS_LABEL}, - description: ( - - {filterManager.getFilters().map((filter, index) => ( - - - {indexPatterns != null ? ( - - ) : ( - - )} - - - ))} - - ), - }, - ]; - } - if (!isEmpty(query.query)) { - items = [ - ...items, - { - title: <>{I18n.QUERY_LABEL}, - description: <>{query.query}, - }, - ]; - } - if (!isEmpty(savedId)) { - items = [ - ...items, - { - title: <>{I18n.SAVED_ID_LABEL}, - description: <>{savedId}, - }, - ]; - } - return items; + return buildQueryBarDescription({ + field, + filters, + filterManager, + query, + savedId, + indexPatterns, + }); } else if (field === 'threats') { const threats: IMitreEnterpriseAttack[] = get(field, value).filter( (threat: IMitreEnterpriseAttack) => threat.tactic.name !== 'none' ); - if (threats.length > 0) { - return [ - { - title: label, - description: ( - - {threats.map((threat, index) => { - const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); - return ( - - -
- - {tactic != null ? tactic.text : ''} - -
- { - const myTechnique = techniquesOptions.find( - t => t.name === technique.name - ); - return { - label: myTechnique != null ? myTechnique.label : '', - href: technique.reference, - target: '_blank', - }; - })} - /> -
-
- ); - })} -
- ), - }, - ]; - } - return []; + return buildThreatsDescription({ label, threats }); } else if (field === 'description') { return [ { @@ -234,27 +126,23 @@ const getDescriptionItem = ( description: , }, ]; + } else if (field === 'references') { + const urls: string[] = get(field, value); + return buildUrlsDescription(label, urls); } else if (Array.isArray(get(field, value))) { const values: string[] = get(field, value); - if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { - return [ - { - title: label, - description: ( - - {values.map((val: string) => - isEmpty(val) ? null : ( - - {val} - - ) - )} - - ), - }, - ]; - } - return []; + return buildStringArrayDescription(label, field, values); + } else if (field === 'severity') { + const val: string = get(field, value); + return buildSeverityDescription(label, val); + } else if (field === 'timeline') { + const timeline = get(field, value) as FieldValueTimeline; + return [ + { + title: label, + description: timeline.title ?? DEFAULT_TIMELINE_TITLE, + }, + ]; } const description: string = get(field, value); if (!isEmpty(description)) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/types.ts new file mode 100644 index 00000000000000..d32fbcd725d12f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ReactNode } from 'react'; + +import { + IIndexPattern, + esFilters, + FilterManager, + Query, +} from '../../../../../../../../../../src/plugins/data/public'; +import { IMitreEnterpriseAttack } from '../../types'; + +export interface ListItems { + title: NonNullable; + description: NonNullable; +} + +export interface BuildQueryBarDescription { + field: string; + filters: esFilters.Filter[]; + filterManager: FilterManager; + query: Query; + savedId: string; + indexPatterns?: IIndexPattern; +} + +export interface BuildThreatsDescription { + label: string; + threats: IMitreEnterpriseAttack[]; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx index 381a3138bf6179..e10194853e7f93 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ImportRuleModalComponent } from './index'; jest.mock('../../../../../lib/kibana'); @@ -20,6 +19,6 @@ describe('ImportRuleModal', () => { importComplete={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx index 6d50fb768068fe..75be92f2fe8467 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx @@ -28,8 +28,6 @@ import { fold } from 'fp-ts/lib/Either'; import uuid from 'uuid'; import { duplicateRules, RulesSchema } from '../../../../../containers/detection_engine/rules'; -import { useUiSetting$ } from '../../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../../../common/constants'; import { useStateToaster } from '../../../../../components/toasters'; import { ndjsonToJSON } from '../json_downloader'; import * as i18n from './translations'; @@ -54,7 +52,6 @@ export const ImportRuleModalComponent = ({ }: ImportRuleModalProps) => { const [selectedFiles, setSelectedFiles] = useState(null); const [isImporting, setIsImporting] = useState(false); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); const cleanupAndCloseModal = () => { @@ -89,7 +86,7 @@ export const ImportRuleModalComponent = ({ }, identity) ); - const duplicatedRules = await duplicateRules({ rules: decodedRules, kbnVersion }); + const duplicatedRules = await duplicateRules({ rules: decodedRules }); importComplete(); cleanupAndCloseModal(); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx index d7a508e2c53e38..859918cdc8e605 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { JSONDownloaderComponent, jsonToNDJSON, ndjsonToJSON } from './index'; const jsonArray = [ @@ -37,7 +36,7 @@ describe('JSONDownloader', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('jsonToNDJSON', () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.ts new file mode 100644 index 00000000000000..1202fe54ad194f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { isEmpty } from 'lodash/fp'; + +import { IMitreAttack } from '../../types'; + +export const isMitreAttackInvalid = ( + tacticName: string | null | undefined, + techniques: IMitreAttack[] | null | undefined +) => { + if (isEmpty(tacticName) || (tacticName !== 'none' && isEmpty(techniques))) { + return true; + } + return false; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx index a777506ee12ae5..97c4c2fdd050aa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx @@ -8,27 +8,30 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiFormRow, - EuiSelect, + EuiSuperSelect, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiComboBox, - EuiFormControlLayout, + EuiText, } from '@elastic/eui'; import { isEmpty, kebabCase, camelCase } from 'lodash/fp'; -import React, { ChangeEvent, useCallback } from 'react'; +import React, { useCallback } from 'react'; import styled from 'styled-components'; import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; -import * as RuleI18n from '../../translations'; +import * as Rulei18n from '../../translations'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; -import * as I18n from './translations'; +import { threatsDefault } from '../step_about_rule/default_value'; import { IMitreEnterpriseAttack } from '../../types'; +import { isMitreAttackInvalid } from './helpers'; +import * as i18n from './translations'; -const MyEuiFormControlLayout = styled(EuiFormControlLayout)` - &.euiFormControlLayout--compressed { - height: fit-content !important; - } +const MitreContainer = styled.div` + margin-top: 16px; +`; +const MyEuiSuperSelect = styled(EuiSuperSelect)` + width: 280px; `; interface AddItemProps { field: FieldHook; @@ -43,7 +46,12 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI const removeItem = useCallback( (index: number) => { const values = field.value as string[]; - field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + const newValues = [...values.slice(0, index), ...values.slice(index + 1)]; + if (isEmpty(newValues)) { + field.setValue(threatsDefault); + } else { + field.setValue(newValues); + } }, [field] ); @@ -61,9 +69,9 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI }, [field]); const updateTactic = useCallback( - (index: number, event: ChangeEvent) => { + (index: number, value: string) => { const values = field.value as IMitreEnterpriseAttack[]; - const { id, reference, name } = tacticsOptions.find(t => t.value === event.target.value) || { + const { id, reference, name } = tacticsOptions.find(t => t.value === value) || { id: '', name: '', reference: '', @@ -97,75 +105,104 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI const values = field.value as IMitreEnterpriseAttack[]; + const getSelectTactic = (tacticName: string, index: number, disabled: boolean) => ( + {i18n.TACTIC_PLACEHOLDER}, + value: 'none', + disabled, + }, + ] + : []), + ...tacticsOptions.map(t => ({ + inputDisplay: <>{t.text}, + value: t.value, + disabled, + })), + ]} + aria-label="" + onChange={updateTactic.bind(null, index)} + fullWidth={false} + valueOfSelected={camelCase(tacticName)} + /> + ); + + const getSelectTechniques = (item: IMitreEnterpriseAttack, index: number, disabled: boolean) => { + const invalid = isMitreAttackInvalid(item.tactic.name, item.techniques); + return ( + + + t.tactics.includes(kebabCase(item.tactic.name)))} + selectedOptions={item.techniques} + onChange={updateTechniques.bind(null, index)} + isDisabled={disabled} + fullWidth={true} + isInvalid={invalid} + /> + {invalid && ( + +

{errorMessage}

+
+ )} +
+ + removeItem(index)} + aria-label={Rulei18n.DELETE} + /> + +
+ ); + }; + return ( - - <> - {values.map((item, index) => { - const euiSelectFieldProps = { - disabled: isDisabled, - }; - return ( -
- - - ({ text: t.text, value: t.value })), - ]} - aria-label="" - onChange={updateTactic.bind(null, index)} - prepend={I18n.TACTIC} - compressed - fullWidth={false} - value={camelCase(item.tactic.name)} - {...euiSelectFieldProps} - /> - - - - - t.tactics.includes(kebabCase(item.tactic.name)) - )} - selectedOptions={item.techniques} - onChange={updateTechniques.bind(null, index)} - isDisabled={isDisabled} - fullWidth={true} - /> - - - - removeItem(index)} - aria-label={RuleI18n.DELETE} - /> - - - {values.length - 1 !== index && } -
- ); - })} - - {I18n.ADD_MITRE_ATTACK} - - -
+ + {values.map((item, index) => ( +
+ + + {index === 0 ? ( + + <>{getSelectTactic(item.tactic.name, index, isDisabled)} + + ) : ( + getSelectTactic(item.tactic.name, index, isDisabled) + )} + + + {index === 0 ? ( + + <>{getSelectTechniques(item, index, isDisabled)} + + ) : ( + getSelectTechniques(item, index, isDisabled) + )} + + + {values.length - 1 !== index && } +
+ ))} + + {i18n.ADD_MITRE_ATTACK} + +
); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts index 22ee6cc3ef911f..dd4c55c1503ec4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; export const TACTIC = i18n.translate('xpack.siem.detectionEngine.mitreAttack.tacticsDescription', { - defaultMessage: 'Tactic', + defaultMessage: 'tactic', }); -export const TECHNIQUES = i18n.translate( +export const TECHNIQUE = i18n.translate( 'xpack.siem.detectionEngine.mitreAttack.techniquesDescription', { - defaultMessage: 'Techniques', + defaultMessage: 'technique', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx new file mode 100644 index 00000000000000..873e0c2184c61d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.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 { EuiFormRow } from '@elastic/eui'; +import React, { useCallback, useEffect, useState } from 'react'; + +import { SearchTimelineSuperSelect } from '../../../../../components/timeline/search_super_select'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; + +export interface FieldValueTimeline { + id: string | null; + title: string | null; +} + +interface QueryBarDefineRuleProps { + dataTestSubj: string; + field: FieldHook; + idAria: string; + isDisabled: boolean; +} + +export const PickTimeline = ({ + dataTestSubj, + field, + idAria, + isDisabled = false, +}: QueryBarDefineRuleProps) => { + const [timelineId, setTimelineId] = useState(null); + const [timelineTitle, setTimelineTitle] = useState(null); + + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + useEffect(() => { + const { id, title } = field.value as FieldValueTimeline; + if (timelineTitle !== title && timelineId !== id) { + setTimelineId(id); + setTimelineTitle(title); + } + }, [field.value]); + + const handleOnTimelineChange = useCallback( + (title: string, id: string | null) => { + if (id === null) { + field.setValue({ id, title: null }); + } else if (timelineTitle !== title && timelineId !== id) { + field.setValue({ id, title }); + } + }, + [field] + ); + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx index c294ec24c4cb70..3e39beb6e61b78 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx @@ -6,7 +6,7 @@ import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; @@ -19,11 +19,18 @@ import { SavedQueryTimeFilter, } from '../../../../../../../../../../src/plugins/data/public'; +import { BrowserFields } from '../../../../../containers/source'; +import { OpenTimelineModal } from '../../../../../components/open_timeline/open_timeline_modal'; +import { ActionTimelineToShow } from '../../../../../components/open_timeline/types'; import { QueryBar } from '../../../../../components/query_bar'; +import { buildGlobalQuery } from '../../../../../components/timeline/helpers'; +import { getDataProviderFilter } from '../../../../../components/timeline/query_bar'; +import { convertKueryToElasticSearchQuery } from '../../../../../lib/keury'; import { useKibana } from '../../../../../lib/kibana'; +import { TimelineModel } from '../../../../../store/timeline/model'; import { useSavedQueryServices } from '../../../../../utils/saved_query_services'; - import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; +import * as i18n from './translations'; export interface FieldValueQueryBar { filters: esFilters.Filter[]; @@ -31,11 +38,14 @@ export interface FieldValueQueryBar { saved_id: string | null; } interface QueryBarDefineRuleProps { + browserFields: BrowserFields; dataTestSubj: string; field: FieldHook; idAria: string; isLoading: boolean; indexPattern: IIndexPattern; + onCloseTimelineSearch: () => void; + openTimelineSearch: boolean; resizeParentContainer?: (height: number) => void; } @@ -56,14 +66,18 @@ const StyledEuiFormRow = styled(EuiFormRow)` // TODO need to add disabled in the SearchBar export const QueryBarDefineRule = ({ + browserFields, dataTestSubj, field, idAria, indexPattern, isLoading = false, + onCloseTimelineSearch, + openTimelineSearch = false, resizeParentContainer, }: QueryBarDefineRuleProps) => { const [originalHeight, setOriginalHeight] = useState(-1); + const [loadingTimeline, setLoadingTimeline] = useState(false); const [savedQuery, setSavedQuery] = useState(null); const [queryDraft, setQueryDraft] = useState({ query: '', language: 'kuery' }); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); @@ -168,6 +182,38 @@ export const QueryBarDefineRule = ({ [field.value] ); + const onCloseTimelineModal = useCallback(() => { + setLoadingTimeline(true); + onCloseTimelineSearch(); + }, [onCloseTimelineSearch]); + + const onOpenTimeline = useCallback( + (timeline: TimelineModel) => { + setLoadingTimeline(false); + const newQuery = { + query: timeline.kqlQuery.filterQuery?.kuery?.expression ?? '', + language: timeline.kqlQuery.filterQuery?.kuery?.kind ?? 'kuery', + }; + const dataProvidersDsl = + timeline.dataProviders != null && timeline.dataProviders.length > 0 + ? convertKueryToElasticSearchQuery( + buildGlobalQuery(timeline.dataProviders, browserFields), + indexPattern + ) + : ''; + const newFilters = timeline.filters ?? []; + field.setValue({ + filters: + dataProvidersDsl !== '' + ? [...newFilters, getDataProviderFilter(dataProvidersDsl)] + : newFilters, + query: newQuery, + saved_id: '', + }); + }, + [browserFields, field, indexPattern] + ); + const onMutation = (event: unknown, observer: unknown) => { if (resizeParentContainer != null) { const suggestionContainer = document.getElementById('kbnTypeahead__items'); @@ -189,39 +235,51 @@ export const QueryBarDefineRule = ({ } }; + const actionTimelineToHide = useMemo(() => ['duplicate'], []); + return ( - - + - {mutationRef => ( -
- -
- )} -
-
+ + {mutationRef => ( +
+ +
+ )} +
+ + {openTimelineSearch ? ( + + ) : null} + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/translations.tsx new file mode 100644 index 00000000000000..9b14e4f8599dac --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/translations.tsx @@ -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 { i18n } from '@kbn/i18n'; + +export const IMPORT_TIMELINE_MODAL = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.importTimelineModalTitle', + { + defaultMessage: 'Import query from saved timeline', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx index 7a3a99b1839718..104eff34c91b31 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.test.tsx @@ -5,17 +5,15 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { RuleSwitchComponent } from './index'; -jest.mock('../../../../../lib/kibana'); describe('RuleSwitch', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx index 54f0fc453830e1..a1fa4770a41acc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx @@ -15,9 +15,7 @@ import { isEmpty } from 'lodash/fp'; import styled from 'styled-components'; import React, { useCallback, useState, useEffect } from 'react'; -import { DEFAULT_KBN_VERSION } from '../../../../../../common/constants'; import { enableRules } from '../../../../../containers/detection_engine/rules'; -import { useUiSetting$ } from '../../../../../lib/kibana'; import { enableRulesAction } from '../../all/actions'; import { Action } from '../../all/reducer'; @@ -50,19 +48,17 @@ export const RuleSwitchComponent = ({ }: RuleSwitchProps) => { const [myIsLoading, setMyIsLoading] = useState(false); const [myEnabled, setMyEnabled] = useState(enabled ?? false); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const onRuleStateChange = useCallback( async (event: EuiSwitchEvent) => { setMyIsLoading(true); if (dispatch != null) { - await enableRulesAction([id], event.target.checked!, dispatch, kbnVersion); + await enableRulesAction([id], event.target.checked!, dispatch); } else { try { const updatedRules = await enableRules({ ids: [id], enabled: event.target.checked!, - kbnVersion, }); setMyEnabled(updatedRules[0].enabled); } catch { @@ -71,7 +67,7 @@ export const RuleSwitchComponent = ({ } setMyIsLoading(false); }, - [dispatch, id, kbnVersion] + [dispatch, id] ); useEffect(() => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx index 2e57ff8ba2c4f0..8097c27cddfe8b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFieldNumber, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiFieldNumber, EuiFormRow, EuiSelect } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; @@ -32,6 +32,10 @@ const StyledEuiFormRow = styled(EuiFormRow)` } `; +const MyEuiSelect = styled(EuiSelect)` + width: auto; +`; + export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: ScheduleItemProps) => { const [timeType, setTimeType] = useState('s'); const [timeVal, setTimeVal] = useState(0); @@ -79,22 +83,33 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu // EUI missing some props const rest = { disabled: isDisabled }; + const label = useMemo( + () => ( + + + {field.label} + + + {field.labelAppend} + + + ), + [field.label, field.labelAppend] + ); return ( } - compressed fullWidth min={0} onChange={onChangeTimeVal} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx index 48ff0d80d0398f..3ec5bf1a12eb0f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx @@ -27,7 +27,7 @@ const RuleStatusIconStyled = styled.div` const RuleStatusIconComponent: React.FC = ({ name, type }) => { const theme = useEuiTheme(); - const color = type === 'passive' ? theme.euiColorLightestShade : theme.euiColorDarkestShade; + const color = type === 'passive' ? theme.euiColorLightestShade : theme.euiColorPrimary; return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts deleted file mode 100644 index 7d6e434bcc8c6c..00000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.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 * as I18n from './translations'; - -export type SeverityValue = 'low' | 'medium' | 'high' | 'critical'; - -interface SeverityOptionItem { - value: SeverityValue; - text: string; -} - -export const severityOptions: SeverityOptionItem[] = [ - { value: 'low', text: I18n.LOW }, - { value: 'medium', text: I18n.MEDIUM }, - { value: 'high', text: I18n.HIGH }, - { value: 'critical', text: I18n.CRITICAL }, -]; - -export const defaultRiskScoreBySeverity: Record = { - low: 21, - medium: 47, - high: 73, - critical: 99, -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx new file mode 100644 index 00000000000000..9fb64189ebd1af --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.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 { EuiHealth } from '@elastic/eui'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import React from 'react'; +import * as I18n from './translations'; + +export type SeverityValue = 'low' | 'medium' | 'high' | 'critical'; + +interface SeverityOptionItem { + value: SeverityValue; + inputDisplay: React.ReactElement; +} + +export const severityOptions: SeverityOptionItem[] = [ + { + value: 'low', + inputDisplay: {I18n.LOW}, + }, + { + value: 'medium', + inputDisplay: {I18n.MEDIUM} , + }, + { + value: 'high', + inputDisplay: {I18n.HIGH} , + }, + { + value: 'critical', + inputDisplay: {I18n.CRITICAL} , + }, +]; + +export const defaultRiskScoreBySeverity: Record = { + low: 21, + medium: 47, + high: 73, + critical: 99, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts index c0c5ae77a1960e..328c4a0f960667 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts @@ -6,6 +6,14 @@ import { AboutStepRule } from '../../types'; +export const threatsDefault = [ + { + framework: 'MITRE ATT&CK', + tactic: { id: 'none', name: 'none', reference: 'none' }, + techniques: [], + }, +]; + export const stepAboutDefaultValue: AboutStepRule = { name: '', description: '', @@ -15,11 +23,9 @@ export const stepAboutDefaultValue: AboutStepRule = { references: [''], falsePositives: [''], tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { id: 'none', name: 'none', reference: 'none' }, - techniques: [], - }, - ], + timeline: { + id: null, + title: null, + }, + threats: threatsDefault, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.ts new file mode 100644 index 00000000000000..99b01c8b22974c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; + +const urlExpression = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi; + +export const isUrlInvalid = (url: string | null | undefined) => { + if (!isEmpty(url) && url != null && url.match(urlExpression) == null) { + return true; + } + return false; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index e266c0b9ab47d6..8956776dcd3b21 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -7,17 +7,21 @@ import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual, get } from 'lodash/fp'; import React, { memo, useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; import * as RuleI18n from '../../translations'; -import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { AddItem } from '../add_item_form'; +import { StepRuleDescription } from '../description_step'; +import { AddMitreThreat } from '../mitre'; +import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; + import { defaultRiskScoreBySeverity, severityOptions, SeverityValue } from './data'; import { stepAboutDefaultValue } from './default_value'; +import { isUrlInvalid } from './helpers'; import { schema } from './schema'; import * as I18n from './translations'; -import { StepRuleDescription } from '../description_step'; -import { AddMitreThreat } from '../mitre'; +import { PickTimeline } from '../pick_timeline'; const CommonUseField = getUseField({ component: Field }); @@ -25,6 +29,10 @@ interface StepAboutRuleProps extends RuleStepProps { defaultValues?: AboutStepRule | null; } +const TagContainer = styled.div` + margin-top: 16px; +`; + export const StepAboutRule = memo( ({ defaultValues, @@ -90,7 +98,6 @@ export const StepAboutRule = memo( idAria: 'detectionEngineStepAboutRuleName', 'data-test-subj': 'detectionEngineStepAboutRuleName', euiFieldProps: { - compressed: true, fullWidth: false, disabled: isLoading, }, @@ -99,11 +106,9 @@ export const StepAboutRule = memo( ( idAria: 'detectionEngineStepAboutRuleSeverity', 'data-test-subj': 'detectionEngineStepAboutRuleSeverity', euiFieldProps: { - compressed: true, fullWidth: false, disabled: isLoading, options: severityOptions, @@ -129,29 +133,38 @@ export const StepAboutRule = memo( euiFieldProps: { max: 100, min: 0, - compressed: true, fullWidth: false, disabled: isLoading, options: severityOptions, + showTicks: true, + tickInterval: 25, }, }} /> + ( path="threats" component={AddMitreThreat} componentProps={{ - compressed: true, idAria: 'detectionEngineStepAboutRuleMitreThreats', isDisabled: isLoading, dataTestSubj: 'detectionEngineStepAboutRuleMitreThreats', }} /> - + + + {({ severity }) => { const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; @@ -202,7 +216,7 @@ export const StepAboutRule = memo( > - {myStepData.isNew ? RuleI18n.CONTINUE : RuleI18n.UPDATE} + {RuleI18n.CONTINUE} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index c72312bb908361..9355f1c8bfefa0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -6,7 +6,6 @@ import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash/fp'; import React from 'react'; import * as RuleI18n from '../../translations'; @@ -18,6 +17,8 @@ import { ValidationFunc, ERROR_CODE, } from '../shared_imports'; +import { isMitreAttackInvalid } from '../mitre/helpers'; +import { isUrlInvalid } from './helpers'; import * as I18n from './translations'; const { emptyField } = fieldValidators; @@ -63,7 +64,7 @@ export const schema: FormSchema = { ], }, severity: { - type: FIELD_TYPES.SELECT, + type: FIELD_TYPES.SUPER_SELECT, label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldSeverityLabel', { @@ -92,6 +93,14 @@ export const schema: FormSchema = { } ), }, + timeline: { + label: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateLabel', + { + defaultMessage: 'Timeline template', + } + ), + }, references: { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel', @@ -100,6 +109,28 @@ export const schema: FormSchema = { } ), labelAppend: {RuleI18n.OPTIONAL_FIELD}, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path }] = args; + let hasError = false; + (value as string[]).forEach(url => { + if (isUrlInvalid(url)) { + hasError = true; + } + }); + return hasError + ? { + code: 'ERR_FIELD_FORMAT', + path, + message: I18n.URL_FORMAT_INVALID, + } + : undefined; + }, + }, + ], }, falsePositives: { label: i18n.translate( @@ -126,7 +157,7 @@ export const schema: FormSchema = { const [{ value, path }] = args; let hasError = false; (value as IMitreEnterpriseAttack[]).forEach(v => { - if (isEmpty(v.tactic.name) || (v.tactic.name !== 'none' && isEmpty(v.techniques))) { + if (isMitreAttackInvalid(v.tactic.name, v.techniques)) { hasError = true; } }); @@ -146,6 +177,13 @@ export const schema: FormSchema = { label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsLabel', { defaultMessage: 'Tags', }), + helpText: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsHelpText', + { + defaultMessage: + 'Type one or more custom identifying tags for this rule. Press enter after each tag to begin a new one.', + } + ), labelAppend: {RuleI18n.OPTIONAL_FIELD}, }, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts index 017d4fe6fdf49e..052986480e9abf 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts @@ -54,3 +54,10 @@ export const CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED = i18n.translate( defaultMessage: 'At least one Technique is required with a Tactic.', } ); + +export const URL_FORMAT_INVALID = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError', + { + defaultMessage: 'Url is invalid format', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx index cc4e959cc9c78b..ecd2ce442238fa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx @@ -4,8 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import { isEqual, get } from 'lodash/fp'; +import { + EuiButtonEmpty, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiButton, +} from '@elastic/eui'; +import { isEmpty, isEqual, get } from 'lodash/fp'; import React, { memo, useCallback, useState, useEffect } from 'react'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; @@ -18,7 +24,7 @@ import { StepRuleDescription } from '../description_step'; import { QueryBarDefineRule } from '../query_bar'; import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { schema } from './schema'; -import * as I18n from './translations'; +import * as i18n from './translations'; const CommonUseField = getUseField({ component: Field }); @@ -34,7 +40,6 @@ const stepDefineDefaultValue = { filters: [], saved_id: null, }, - useIndicesConfig: 'true', }; const getStepDefaultValue = ( @@ -45,7 +50,6 @@ const getStepDefaultValue = ( return { ...defaultValues, isNew: false, - useIndicesConfig: `${isEqual(defaultValues.index, indicesConfig)}`, }; } else { return { @@ -66,13 +70,22 @@ export const StepDefineRule = memo( setForm, setStepData, }) => { - const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); + const [openTimelineSearch, setOpenTimelineSearch] = useState(false); + const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false); const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); + const [mylocalIndicesConfig, setMyLocalIndicesConfig] = useState( + defaultValues != null ? defaultValues.index : indicesConfig ?? [] + ); const [ - { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, - setIndices, - ] = useFetchIndexPatterns(defaultValues != null ? defaultValues.index : indicesConfig ?? []); - const [myStepData, setMyStepData] = useState(stepDefineDefaultValue); + { + browserFields, + indexPatterns: indexPatternQueryBar, + isLoading: indexPatternLoadingQueryBar, + }, + ] = useFetchIndexPatterns(mylocalIndicesConfig); + const [myStepData, setMyStepData] = useState( + getStepDefaultValue(indicesConfig, null) + ); const { form } = useForm({ defaultValue: myStepData, @@ -96,7 +109,7 @@ export const StepDefineRule = memo( const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); if (!isEqual(myDefaultValues, myStepData)) { setMyStepData(myDefaultValues); - setLocalUseIndicesConfig(myDefaultValues.useIndicesConfig); + setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig)); if (!isReadOnlyView) { Object.keys(schema).forEach(key => { const val = get(key, myDefaultValues); @@ -115,6 +128,19 @@ export const StepDefineRule = memo( } }, [form]); + const handleResetIndices = useCallback(() => { + const indexField = form.getFields().index; + indexField.setValue(indicesConfig); + }, [form, indicesConfig]); + + const handleOpenTimelineSearch = useCallback(() => { + setOpenTimelineSearch(true); + }, []); + + const handleCloseTimelineSearch = useCallback(() => { + setOpenTimelineSearch(false); + }, []); + return isReadOnlyView && myStepData != null ? ( ( ) : ( <>
- + {i18n.RESET_DEFAULT_INDEX} + + ) : null, + }} componentProps={{ idAria: 'detectionEngineStepDefineRuleIndices', 'data-test-subj': 'detectionEngineStepDefineRuleIndices', euiFieldProps: { - compressed: true, fullWidth: true, isDisabled: isLoading, + placeholder: '', }, }} /> + {i18n.IMPORT_TIMELINE_QUERY} + + ), + }} component={QueryBarDefineRule} componentProps={{ - compressed: true, + browserFields, loading: indexPatternLoadingQueryBar, idAria: 'detectionEngineStepDefineRuleQueryBar', indexPattern: indexPatternQueryBar, isDisabled: isLoading, isLoading: indexPatternLoadingQueryBar, dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', + openTimelineSearch, + onCloseTimelineSearch: handleCloseTimelineSearch, resizeParentContainer, }} /> - - {({ useIndicesConfig }) => { - if (localUseIndicesConfig !== useIndicesConfig) { - const indexField = form.getFields().index; - if ( - indexField != null && - useIndicesConfig === 'true' && - !isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue(indicesConfig); - setIndices(indicesConfig); - } else if ( - indexField != null && - useIndicesConfig === 'false' && - isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue([]); - setIndices([]); + + {({ index }) => { + if (index != null) { + if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { + setLocalUseIndicesConfig(true); + } + if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { + setLocalUseIndicesConfig(false); + } + if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { + setMyLocalIndicesConfig(index); } - setLocalUseIndicesConfig(useIndicesConfig); } - return null; }} @@ -208,7 +223,7 @@ export const StepDefineRule = memo( > - {myStepData.isNew ? RuleI18n.CONTINUE : RuleI18n.UPDATE} + {RuleI18n.CONTINUE} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx index 9b54ada8227c61..dbd7e3b3f96aab 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx @@ -10,7 +10,6 @@ import { isEmpty } from 'lodash/fp'; import React from 'react'; import { esKuery } from '../../../../../../../../../../src/plugins/data/public'; -import * as RuleI18n from '../../translations'; import { FieldValueQueryBar } from '../query_bar'; import { ERROR_CODE, @@ -19,33 +18,27 @@ import { FormSchema, ValidationFunc, } from '../shared_imports'; -import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY } from './translations'; +import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT } from './translations'; const { emptyField } = fieldValidators; export const schema: FormSchema = { - useIndicesConfig: { - type: FIELD_TYPES.RADIO_GROUP, + index: { + type: FIELD_TYPES.COMBO_BOX, label: i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepDefineRule.fieldIndicesTypeLabel', + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fiedIndexPatternsLabel', { - defaultMessage: 'Indices type', + defaultMessage: 'Index patterns', } ), - }, - index: { - type: FIELD_TYPES.COMBO_BOX, - label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fiedIndicesLabel', { - defaultMessage: 'Indices', - }), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + helpText: {INDEX_HELPER_TEXT}, validations: [ { validator: emptyField( i18n.translate( 'xpack.siem.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError', { - defaultMessage: 'An output indice name for signals is required.', + defaultMessage: 'Index patterns for signals is required.', } ) ), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx index 0050c59a4a2c8b..8394f090e346cd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx @@ -33,3 +33,25 @@ export const CUSTOM_INDICES = i18n.translate( defaultMessage: 'Provide custom list of indices', } ); + +export const INDEX_HELPER_TEXT = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.indicesHelperDescription', + { + defaultMessage: + 'Enter the pattern of Elasticsearch indices where you would like this rule to run. By default, these will include index patterns defined in SIEM advanced settings.', + } +); + +export const RESET_DEFAULT_INDEX = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton', + { + defaultMessage: 'Reset to default index patterns', + } +); + +export const IMPORT_TIMELINE_QUERY = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.importTimelineQueryButton', + { + defaultMessage: 'Import query from saved timeline', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx index 6f7e49bc8ab9a9..35b8ca6650bf6d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx @@ -92,7 +92,6 @@ export const StepScheduleRule = memo( path="interval" component={ScheduleItem} componentProps={{ - compressed: true, idAria: 'detectionEngineStepScheduleRuleInterval', isDisabled: isLoading, dataTestSubj: 'detectionEngineStepScheduleRuleInterval', @@ -102,7 +101,6 @@ export const StepScheduleRule = memo( path="from" component={ScheduleItem} componentProps={{ - compressed: true, idAria: 'detectionEngineStepScheduleRuleFrom', isDisabled: isLoading, dataTestSubj: 'detectionEngineStepScheduleRuleFrom', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts index a25ccce569dd43..12bbdbdfff3e92 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts @@ -40,14 +40,14 @@ const getTimeTypeValue = (time: string): { unit: string; value: number } => { }; const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => { - const { queryBar, useIndicesConfig, isNew, ...rest } = defineStepData; + const { queryBar, isNew, ...rest } = defineStepData; const { filters, query, saved_id: savedId } = queryBar; return { ...rest, language: query.language, filters, query: query.query as string, - ...(savedId != null ? { saved_id: savedId } : {}), + ...(savedId != null && savedId !== '' ? { saved_id: savedId } : {}), }; }; @@ -72,11 +72,21 @@ const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRul }; const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { - const { falsePositives, references, riskScore, threats, isNew, ...rest } = aboutStepData; + const { + falsePositives, + references, + riskScore, + threats, + timeline, + isNew, + ...rest + } = aboutStepData; return { false_positives: falsePositives.filter(item => !isEmpty(item)), references: references.filter(item => !isEmpty(item)), risk_score: riskScore, + timeline_id: timeline.id, + timeline_title: timeline.title, threats: threats .filter(threat => threat.tactic.name !== 'none') .map(threat => ({ @@ -97,7 +107,7 @@ export const formatRule = ( scheduleData: ScheduleStepRule, ruleId?: string ): NewRule => { - const type: FormatRuleType = defineStepData.queryBar.saved_id != null ? 'saved_query' : 'query'; + const type: FormatRuleType = !isEmpty(defineStepData.queryBar.saved_id) ? 'saved_query' : 'query'; const persistData = { type, ...formatDefineStepData(defineStepData), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index 3e8dbeba895461..848b17aadbff4d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -12,12 +12,13 @@ import styled from 'styled-components'; import { HeaderPage } from '../../../../components/header_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { WrapperPage } from '../../../../components/wrapper_page'; +import { usePersistRule } from '../../../../containers/detection_engine/rules'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; import { AccordionTitle } from '../components/accordion_title'; +import { FormData, FormHook } from '../components/shared_imports'; import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; -import { usePersistRule } from '../../../../containers/detection_engine/rules'; -import { SpyRoute } from '../../../../utils/route/spy_routes'; import * as RuleI18n from '../translations'; import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; @@ -28,17 +29,43 @@ const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.schedu const ResizeEuiPanel = styled(EuiPanel)<{ height?: number; }>` + .euiAccordion__iconWrapper { + display: none; + } .euiAccordion__childWrapper { height: ${props => (props.height !== -1 ? `${props.height}px !important` : 'auto')}; } + .euiAccordion__button { + cursor: default !important; + &:hover { + text-decoration: none !important; + } + } +`; + +const MyEuiPanel = styled(EuiPanel)` + .euiAccordion__iconWrapper { + display: none; + } + .euiAccordion__button { + cursor: default !important; + &:hover { + text-decoration: none !important; + } + } `; export const CreateRuleComponent = React.memo(() => { const [heightAccordion, setHeightAccordion] = useState(-1); - const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); + const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); const scheduleRuleRef = useRef(null); + const stepsForm = useRef | null>>({ + [RuleStep.defineRule]: null, + [RuleStep.aboutRule]: null, + [RuleStep.scheduleRule]: null, + }); const stepsData = useRef>({ [RuleStep.defineRule]: { isValid: false, data: {} }, [RuleStep.aboutRule]: { isValid: false, data: {} }, @@ -57,11 +84,17 @@ export const CreateRuleComponent = React.memo(() => { if (isValid) { const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); if ([0, 1].includes(stepRuleIdx)) { - setIsStepRuleInEditView({ - ...isStepRuleInReadOnlyView, - [step]: true, - }); - if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) { + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + [stepsRuleOrder[stepRuleIdx + 1]]: false, + }); + } else if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + }); openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); } @@ -80,9 +113,13 @@ export const CreateRuleComponent = React.memo(() => { } } }, - [openAccordionId, stepsData.current, setRule] + [isStepRuleInReadOnlyView, openAccordionId, stepsData.current, setRule] ); + const setStepsForm = useCallback((step: RuleStep, form: FormHook) => { + stepsForm.current[step] = form; + }, []); + const getAccordionType = useCallback( (accordionId: RuleStep) => { if (accordionId === openAccordionId) { @@ -135,42 +172,38 @@ export const CreateRuleComponent = React.memo(() => { (id: RuleStep, isOpen: boolean) => { const activeRuleIdx = stepsRuleOrder.findIndex(step => step === openAccordionId); const stepRuleIdx = stepsRuleOrder.findIndex(step => step === id); - const isLatestStepsRuleValid = - stepRuleIdx === 0 - ? true - : stepsRuleOrder - .filter((stepRule, index) => index < stepRuleIdx) - .every(stepRule => stepsData.current[stepRule].isValid); - if (stepRuleIdx < activeRuleIdx && !isOpen) { + if ((id === openAccordionId || stepRuleIdx < activeRuleIdx) && !isOpen) { openCloseAccordion(id); } else if (stepRuleIdx >= activeRuleIdx) { if ( - openAccordionId != null && openAccordionId !== id && !stepsData.current[openAccordionId].isValid && !isStepRuleInReadOnlyView[id] && isOpen ) { openCloseAccordion(id); - } else if (!isLatestStepsRuleValid && isOpen) { - openCloseAccordion(id); - } else if (id !== openAccordionId && isOpen) { - setOpenAccordionId(id); } } }, - [isStepRuleInReadOnlyView, openAccordionId] + [isStepRuleInReadOnlyView, openAccordionId, stepsData] ); const manageIsEditable = useCallback( - (id: RuleStep) => { - setIsStepRuleInEditView({ - ...isStepRuleInReadOnlyView, - [id]: false, - }); + async (id: RuleStep) => { + const activeForm = await stepsForm.current[openAccordionId]?.submit(); + if (activeForm != null && activeForm?.isValid) { + setOpenAccordionId(id); + openCloseAccordion(openAccordionId); + + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [openAccordionId]: openAccordionId === RuleStep.scheduleRule ? false : true, + [id]: false, + }); + } }, - [isStepRuleInReadOnlyView] + [isStepRuleInReadOnlyView, openAccordionId] ); if (isSaved) { @@ -201,7 +234,7 @@ export const CreateRuleComponent = React.memo(() => { size="xs" onClick={manageIsEditable.bind(null, RuleStep.defineRule)} > - {`Edit`} + {i18n.EDIT_RULE} ) } @@ -210,13 +243,14 @@ export const CreateRuleComponent = React.memo(() => { setHeightAccordion(height)} /> - + { size="xs" onClick={manageIsEditable.bind(null, RuleStep.aboutRule)} > - {`Edit`} + {i18n.EDIT_RULE} ) } @@ -240,12 +274,13 @@ export const CreateRuleComponent = React.memo(() => { - + - + { size="xs" onClick={manageIsEditable.bind(null, RuleStep.scheduleRule)} > - {`Edit`} + {i18n.EDIT_RULE} ) } @@ -269,10 +304,11 @@ export const CreateRuleComponent = React.memo(() => { - + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts index 884f3f3741228a..329bcc286fb70c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts @@ -9,3 +9,7 @@ import { i18n } from '@kbn/i18n'; export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.pageTitle', { defaultMessage: 'Create new rule', }); + +export const EDIT_RULE = i18n.translate('xpack.siem.detectionEngine.createRule.editRuleButton', { + defaultMessage: 'Edit', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 1bc2bc24517e3c..4d887c7cb5b6ec 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -113,7 +113,10 @@ export const RuleDetailsComponent = memo(({ signalsIn (({ signalsIn diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 8e32f82dff0b1c..10b7f0e832f192 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -49,6 +49,7 @@ interface ScheduleStepRuleForm extends StepRuleForm { export const EditRuleComponent = memo(() => { const { ruleId } = useParams(); const [loading, rule] = useRule(ruleId); + const [initForm, setInitForm] = useState(false); const [myAboutRuleForm, setMyAboutRuleForm] = useState({ data: null, @@ -249,7 +250,7 @@ export const EditRuleComponent = memo(() => { }, []); if (isSaved || (rule != null && rule.immutable)) { - return ; + return ; } return ( @@ -257,7 +258,7 @@ export const EditRuleComponent = memo(() => { { responsive={false} > - + {i18n.CANCEL} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index 46301ae808919d..47b5c1051bcfca 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -33,7 +33,6 @@ export const getStepsData = ({ filters: rule.filters as esFilters.Filter[], saved_id: rule.saved_id ?? null, }, - useIndicesConfig: 'true', } : null; const aboutRuleData: AboutStepRule | null = @@ -45,6 +44,10 @@ export const getStepsData = ({ threats: rule.threats as IMitreEnterpriseAttack[], falsePositives: rule.false_positives, riskScore: rule.risk_score, + timeline: { + id: rule.timeline_id ?? null, + title: rule.timeline_title ?? null, + }, } : null; const scheduleRuleData: ScheduleStepRule | null = diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 8b4cc2a213589b..ef67f0a7d22c66 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -8,6 +8,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect_to_detection_engine'; import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; import { getEmptyTagValue } from '../../../components/empty_value'; import { HeaderPage } from '../../../components/header_page'; @@ -32,7 +33,10 @@ export const RulesComponent = React.memo(() => { /> { - + {i18n.ADD_NEW_RULE} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts index 9b535034810bd5..ec4206623bad90 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts @@ -8,6 +8,7 @@ import { esFilters } from '../../../../../../../../src/plugins/data/common'; import { Rule } from '../../../containers/detection_engine/rules'; import { FieldValueQueryBar } from './components/query_bar'; import { FormData, FormHook } from './components/shared_imports'; +import { FieldValueTimeline } from './components/pick_timeline'; export interface EuiBasicTableSortTypes { field: string; @@ -76,11 +77,11 @@ export interface AboutStepRule extends StepRuleData { references: string[]; falsePositives: string[]; tags: string[]; + timeline: FieldValueTimeline; threats: IMitreEnterpriseAttack[]; } export interface DefineStepRule extends StepRuleData { - useIndicesConfig: string; index: string[]; queryBar: FieldValueQueryBar; } @@ -108,6 +109,8 @@ export interface AboutStepRuleJson { references: string[]; false_positives: string[]; tags: string[]; + timeline_id: string | null; + timeline_title: string | null; threats: IMitreEnterpriseAttack[]; } diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx index 092c2463419d1c..aafeea6465fb3b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx @@ -16,8 +16,6 @@ import { hostDetailsPagePath } from '../types'; import { type } from './utils'; import { useMountAppended } from '../../../utils/use_mount_appended'; -jest.mock('../../../lib/kibana'); - jest.mock('../../../containers/source', () => ({ indicesExistOrDataTemporarilyUnavailable: () => true, WithSource: ({ diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx index 00dcb5908a98bc..065d91b3fc2faa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx @@ -6,21 +6,23 @@ import { mount } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import { ActionCreator } from 'typescript-fsa'; +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; import '../../mock/match_media'; import { mocksSource } from '../../containers/source/mock'; import { wait } from '../../lib/helpers'; -import { TestProviders } from '../../mock'; +import { apolloClientObservable, TestProviders, mockGlobalState } from '../../mock'; import { InputsModelId } from '../../store/inputs/constants'; import { SiemNavigation } from '../../components/navigation'; +import { inputsActions } from '../../store/inputs'; +import { State, createStore } from '../../store'; import { HostsComponentProps } from './types'; import { Hosts } from './hosts'; - -jest.mock('../../lib/kibana'); +import { HostsTabs } from './hosts_tabs'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -138,4 +140,58 @@ describe('Hosts - rendering', () => { wrapper.update(); expect(wrapper.find(SiemNavigation).exists()).toBe(true); }); + + test('it should add the new filters after init', async () => { + const newFilters: esFilters.Filter[] = [ + { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match_phrase: { + 'host.name': 'ItRocks', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: '', + disabled: false, + key: 'bool', + negate: false, + type: 'custom', + value: + '{"query": {"bool": {"filter": [{"bool": {"should": [{"match_phrase": {"host.name": "ItRocks"}}],"minimum_should_match": 1}}]}}}', + }, + }, + ]; + localSource[0].result.data.source.status.indicesExist = true; + const myState: State = mockGlobalState; + const myStore = createStore(myState, apolloClientObservable); + const wrapper = mount( + + + + + + + + ); + await wait(); + wrapper.update(); + + myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); + wrapper.update(); + expect(wrapper.find(HostsTabs).props().filterQuery).toEqual( + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index 6b69f06b97b833..2c475e4ba6ac58 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -41,7 +41,7 @@ import { HostsTableType } from '../../store/hosts/model'; const KpiHostsComponentManage = manageQuery(KpiHostsComponent); -const HostsComponent = React.memo( +export const HostsComponent = React.memo( ({ deleteQuery, isInitializing, @@ -56,13 +56,12 @@ const HostsComponent = React.memo( const capabilities = React.useContext(MlCapabilitiesContext); const kibana = useKibana(); const { tabName } = useParams(); - const hostsFilters = React.useMemo(() => { if (tabName === HostsTableType.alerts) { return filters.length > 0 ? [...filters, ...filterAlertsHosts] : filterAlertsHosts; } return filters; - }, [tabName]); + }, [tabName, filters]); const narrowDateRange = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx index d624631c1feae4..9a9d1cf085eb95 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import { ActionCreator } from 'typescript-fsa'; @@ -28,8 +27,6 @@ const pop: Action = 'POP'; type GlobalWithFetch = NodeJS.Global & { fetch: jest.Mock }; -jest.mock('../../../lib/kibana'); - // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../../components/search_bar', () => ({ @@ -131,7 +128,7 @@ describe('Ip Details', () => { test('it matches the snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders ipv6 headline', async () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx index 335bb62c5c8526..3a22e800d893fc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx @@ -6,17 +6,18 @@ import { mount } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import '../../mock/match_media'; - +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; import { mocksSource } from '../../containers/source/mock'; -import { TestProviders } from '../../mock'; +import { TestProviders, mockGlobalState, apolloClientObservable } from '../../mock'; +import { State, createStore } from '../../store'; +import { inputsActions } from '../../store/inputs'; import { Network } from './network'; - -jest.mock('../../lib/kibana'); +import { NetworkRoutes } from './navigation'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -113,4 +114,58 @@ describe('rendering - rendering', () => { wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(false); }); + + test('it should add the new filters after init', async () => { + const newFilters: esFilters.Filter[] = [ + { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match_phrase: { + 'host.name': 'ItRocks', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: '', + disabled: false, + key: 'bool', + negate: false, + type: 'custom', + value: + '{"query": {"bool": {"filter": [{"bool": {"should": [{"match_phrase": {"host.name": "ItRocks"}}],"minimum_should_match": 1}}]}}}', + }, + }, + ]; + localSource[0].result.data.source.status.indicesExist = true; + const myState: State = mockGlobalState; + const myStore = createStore(myState, apolloClientObservable); + const wrapper = mount( + + + + + + + + ); + await new Promise(resolve => setTimeout(resolve)); + wrapper.update(); + + myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); + wrapper.update(); + expect(wrapper.find(NetworkRoutes).props().filterQuery).toEqual( + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx index c39935742a2e01..ad3513a6f529e0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -59,7 +59,8 @@ const NetworkComponent = React.memo( return filters.length > 0 ? [...filters, ...filterAlertsNetwork] : filterAlertsNetwork; } return filters; - }, [tabName]); + }, [tabName, filters]); + const narrowDateRange = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx index 300df4a742adf2..eff61bf6a9710a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx @@ -6,7 +6,7 @@ import { mount } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { MemoryRouter } from 'react-router-dom'; @@ -14,8 +14,6 @@ import { TestProviders } from '../../mock'; import { mocksSource } from '../../containers/source/mock'; import { Overview } from './index'; -jest.mock('../../lib/kibana'); - let localSource: Array<{ request: {}; result: { diff --git a/x-pack/legacy/plugins/siem/public/plugin.tsx b/x-pack/legacy/plugins/siem/public/plugin.tsx new file mode 100644 index 00000000000000..44624f497a91be --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/plugin.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 { + AppMountParameters, + CoreSetup, + CoreStart, + PluginInitializerContext, + Plugin as IPlugin, +} from '../../../../../src/core/public'; +import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import { IEmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; +import { IUiActionsStart } from '../../../../../src/plugins/ui_actions/public'; + +export { AppMountParameters, CoreSetup, CoreStart, PluginInitializerContext }; + +export interface SetupPlugins { + home: HomePublicPluginSetup; +} +export interface StartPlugins { + data: DataPublicPluginStart; + embeddable: IEmbeddableStart; + inspector: InspectorStart; + uiActions: IUiActionsStart; +} +export type StartServices = CoreStart & StartPlugins; + +export type Setup = ReturnType; +export type Start = ReturnType; + +export class Plugin implements IPlugin { + public id = 'siem'; + public name = 'SIEM'; + constructor( + // @ts-ignore this is added to satisfy the New Platform typing constraint, + // but we're not leveraging any of its functionality yet. + private readonly initializerContext: PluginInitializerContext + ) {} + + public setup(core: CoreSetup, plugins: SetupPlugins) { + core.application.register({ + id: this.id, + title: this.name, + async mount(context, params) { + const [coreStart, pluginsStart] = await core.getStartServices(); + const { renderApp } = await import('./app'); + + return renderApp(coreStart, pluginsStart as StartPlugins, params); + }, + }); + + return {}; + } + + public start(core: CoreStart, plugins: StartPlugins) { + return {}; + } + + public stop() { + return {}; + } +} diff --git a/x-pack/legacy/plugins/siem/public/register_feature.ts b/x-pack/legacy/plugins/siem/public/register_feature.ts index 36c4e28dbd8748..ca7a22408b6ff2 100644 --- a/x-pack/legacy/plugins/siem/public/register_feature.ts +++ b/x-pack/legacy/plugins/siem/public/register_feature.ts @@ -4,19 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - FeatureCatalogueCategory, - FeatureCatalogueRegistryProvider, -} from 'ui/registry/feature_catalogue'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; +import { APP_ID } from '../common/constants'; -const APP_ID = 'siem'; - -FeatureCatalogueRegistryProvider.register(() => ({ - id: 'siem', +// TODO(rylnd): move this into Plugin.setup once we're on NP +npSetup.plugins.home.featureCatalogue.register({ + id: APP_ID, title: 'SIEM', description: 'Explore security metrics and logs for events and alerts', icon: 'securityAnalyticsApp', path: `/app/${APP_ID}`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, -})); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/case/saved_object_mappings_temp.ts b/x-pack/legacy/plugins/siem/server/lib/case/saved_object_mappings_temp.ts new file mode 100644 index 00000000000000..bd73805600a338 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/case/saved_object_mappings_temp.ts @@ -0,0 +1,91 @@ +/* + * 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. + */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/camelcase */ +import { + NewCaseFormatted, + NewCommentFormatted, +} from '../../../../../../../x-pack/plugins/case/server'; +import { ElasticsearchMappingOf } from '../../utils/typed_elasticsearch_mappings'; + +// Temporary file to write mappings for case +// while Saved Object Mappings API is programmed for the NP +// See: https://github.com/elastic/kibana/issues/50309 + +export const caseSavedObjectType = 'case-workflow'; +export const caseCommentSavedObjectType = 'case-workflow-comment'; + +export const caseSavedObjectMappings: { + [caseSavedObjectType]: ElasticsearchMappingOf; +} = { + [caseSavedObjectType]: { + properties: { + assignees: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + }, + }, + created_at: { + type: 'date', + }, + description: { + type: 'text', + }, + title: { + type: 'keyword', + }, + created_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + }, + }, + state: { + type: 'keyword', + }, + tags: { + type: 'keyword', + }, + case_type: { + type: 'keyword', + }, + }, + }, +}; + +export const caseCommentSavedObjectMappings: { + [caseCommentSavedObjectType]: ElasticsearchMappingOf; +} = { + [caseCommentSavedObjectType]: { + properties: { + comment: { + type: 'text', + }, + created_at: { + type: 'date', + }, + created_by: { + properties: { + full_name: { + type: 'keyword', + }, + username: { + type: 'keyword', + }, + }, + }, + }, + }, +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index edf196b96f5d02..f6ac0435cd7c1b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -52,6 +52,7 @@ export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({ created_at: '2019-12-13T16:40:33.400Z', updated_at: '2019-12-13T16:40:33.400Z', timeline_id: 'timeline-id', + timeline_title: 'timeline-title', }); export const typicalPayload = (): Partial => ({ @@ -271,6 +272,7 @@ export const getResult = (): RuleAlertType => ({ outputIndex: '.siem-signals', savedId: 'some-id', timelineId: 'some-timeline-id', + timelineTitle: 'some-timeline-title', meta: { someMeta: 'someField' }, filters: [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json index afe9bac9d87fec..79fb136afd52ae 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json @@ -36,6 +36,9 @@ "timeline_id": { "type": "keyword" }, + "timeline_title": { + "type": "keyword" + }, "max_signals": { "type": "keyword" }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 256b341fca656a..3d9719a7b248b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -74,6 +74,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou updated_at: updatedAt, references, timeline_id: timelineId, + timeline_title: timelineTitle, version, } = payloadRule; const ruleIdOrUuid = ruleId ?? uuid.v4(); @@ -112,6 +113,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou outputIndex: finalIndex, savedId, timelineId, + timelineTitle, meta, filters, ruleId: ruleIdOrUuid, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 476d5b8a49ba2d..cf8fb2a28288f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -44,6 +44,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -101,6 +102,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = outputIndex: finalIndex, savedId, timelineId, + timelineTitle, meta, filters, ruleId: ruleId != null ? ruleId : uuid.v4(), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index b30b6c791522b0..180a75bdaaeead 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -50,6 +50,7 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -82,6 +83,7 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou outputIndex, savedId, timelineId, + timelineTitle, meta, filters, id, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index ec3d9514fa5db8..6db8a8902915aa 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -38,6 +38,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -77,6 +78,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { outputIndex, savedId, timelineId, + timelineTitle, meta, filters, id, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index b1f61d11458fe3..44d47ad435682c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -79,6 +79,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -141,6 +142,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -205,6 +207,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -269,6 +272,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -331,6 +335,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -396,6 +401,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -461,6 +467,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -526,6 +533,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -642,6 +650,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', version: 1, }; expect(output).toEqual({ @@ -714,6 +723,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', version: 1, }; expect(output).toEqual(expected); @@ -875,6 +885,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', version: 1, }; expect(output).toEqual(expected); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index b9bf3f8a942fcb..714035a4234132 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -85,6 +85,7 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial { test('You can omit the query string when filters are present', () => { expect( - addPrepackagedRulesSchema.validate< - Partial & { meta: string }> - >({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1099,7 +1097,7 @@ describe('add prepackaged rules schema', () => { ).toBeFalsy(); }); - test('validates with timeline_id', () => { + test('validates with timeline_id and timeline_title', () => { expect( addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', @@ -1117,7 +1115,131 @@ describe('add prepackaged rules schema', () => { language: 'kuery', version: 1, timeline_id: 'timeline-id', + timeline_title: 'timeline-title', }).error ).toBeFalsy(); }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: 'timeline-id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: 'timeline-id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: 'timeline-id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts index c993b05cb5f299..49907b4a975e6d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts @@ -21,6 +21,7 @@ import { language, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, @@ -63,6 +64,7 @@ export const addPrepackagedRulesSchema = Joi.object({ otherwise: Joi.forbidden(), }), timeline_id, + timeline_title, meta, risk_score: risk_score.required(), max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 8dc00b66e97a3e..87916bea606493 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -1024,7 +1024,7 @@ describe('create rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - createRulesSchema.validate & { meta: string }>>({ + createRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1045,7 +1045,7 @@ describe('create rules schema', () => { ).toBeFalsy(); }); - test('timeline_id validates', () => { + test('validates with timeline_id and timeline_title', () => { expect( createRulesSchema.validate>({ rule_id: 'rule-1', @@ -1062,8 +1062,122 @@ describe('create rules schema', () => { references: ['index-1'], query: 'some query', language: 'kuery', - timeline_id: 'some_id', + timeline_id: 'timeline-id', + timeline_title: 'timeline-title', }).error ).toBeFalsy(); }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts index 614451312d04da..df5c1694d6c782 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts @@ -22,6 +22,7 @@ import { output_index, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, @@ -57,6 +58,7 @@ export const createRulesSchema = Joi.object({ otherwise: Joi.forbidden(), }), timeline_id, + timeline_title, meta, risk_score: risk_score.required(), max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts index 68d3166c74d6da..c8331bb7820dd1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -24,6 +24,11 @@ export const language = Joi.string().valid('kuery', 'lucene'); export const output_index = Joi.string(); export const saved_id = Joi.string(); export const timeline_id = Joi.string(); +export const timeline_title = Joi.string().when('timeline_id', { + is: Joi.exist(), + then: Joi.required(), + otherwise: Joi.forbidden(), +}); export const meta = Joi.object(); export const max_signals = Joi.number().greater(0); export const name = Joi.string(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index 1f00e0a13866a4..f713840ab43f93 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -867,7 +867,7 @@ describe('update rules schema', () => { ).toBeTruthy(); }); - test('timeline_id validates', () => { + test('validates with timeline_id and timeline_title', () => { expect( updateRulesSchema.validate>({ id: 'rule-1', @@ -881,7 +881,101 @@ describe('update rules schema', () => { type: 'saved_query', saved_id: 'some id', timeline_id: 'some-id', + timeline_title: 'some-title', }).error ).toBeFalsy(); }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: 'some-id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: 'timeline-id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: 'some-id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts index afd8a5fce48331..9c3188738faea7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts @@ -22,6 +22,7 @@ import { output_index, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, @@ -53,6 +54,7 @@ export const updateRulesSchema = Joi.object({ output_index, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index 07cf0b0c716cc0..d2f76907d7aa3e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -19,6 +19,7 @@ export const createRules = async ({ language, savedId, timelineId, + timelineTitle, meta, filters, ruleId, @@ -56,6 +57,7 @@ export const createRules = async ({ outputIndex, savedId, timelineId, + timelineTitle, meta, filters, maxSignals, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 9acfbf8c43221c..9c3be64f71a0dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -26,6 +26,7 @@ export const installPrepackagedRules = async ( language, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -55,6 +56,7 @@ export const installPrepackagedRules = async ( outputIndex, savedId, timelineId, + timelineTitle, meta, filters, ruleId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index c9dac82b6eb8f7..0fe4b15437af85 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -74,6 +74,7 @@ export const updateRules = async ({ outputIndex, savedId, timelineId, + timelineTitle, meta, filters, from, @@ -118,6 +119,7 @@ export const updateRules = async ({ outputIndex, savedId, timelineId, + timelineTitle, meta, filters, index, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json index 2f995029447ffe..eb87a14e0c6885 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json @@ -7,5 +7,6 @@ "from": "now-6m", "to": "now", "query": "user.name: root or user.name: admin", - "timeline_id": "timeline-id" + "timeline_id": "timeline-id", + "timeline_title": "timeline_title" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json index 60095a0a6a8334..46a2feeefd49e3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json @@ -78,5 +78,6 @@ "Some plain text string here explaining why this is a valid thing to look out for" ], "timeline_id": "timeline_id", + "timeline_title": "timeline_title", "version": 1 } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json index 2628b69eb064d7..16d5d6cc2b36a2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json @@ -78,5 +78,6 @@ "Some plain text string here explaining why this is a valid thing to look out for" ], "saved_id": "test-saved-id", - "timeline_id": "test-timeline-id" + "timeline_id": "test-timeline-id", + "timeline_title": "test-timeline-title" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json index 4da285e5b09bf9..7fc8de9fe8f9ec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json @@ -78,5 +78,6 @@ "Some plain text string here explaining why this is a valid thing to look out for" ], "timeline_id": "other-timeline-id", + "timeline_title": "other-timeline-title", "version": 42 } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json index 8cfa3303f54a62..27dee7dd81463c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json @@ -1,4 +1,5 @@ { "rule_id": "query-rule-id", - "timeline_id": "other-timeline-id" + "timeline_id": "other-timeline-id", + "timeline_title": "other-timeline-title" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 5e50b65b517172..ede82a597b238e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -31,6 +31,7 @@ export const sampleRuleAlertParams = ( filters: undefined, savedId: undefined, timelineId: undefined, + timelineTitle: undefined, meta: undefined, threats: undefined, version: 1, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts index 0a3526d32e511a..1093ff3a8a462d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts @@ -34,6 +34,7 @@ export const buildRule = ({ false_positives: ruleParams.falsePositives, saved_id: ruleParams.savedId, timeline_id: ruleParams.timelineId, + timeline_title: ruleParams.timelineTitle, meta: ruleParams.meta, max_signals: ruleParams.maxSignals, risk_score: ruleParams.riskScore, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 87d31abbc5371a..ab2c1733b04ca2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -42,6 +42,7 @@ export const signalRulesAlertType = ({ outputIndex: schema.nullable(schema.string()), savedId: schema.nullable(schema.string()), timelineId: schema.nullable(schema.string()), + timelineTitle: schema.nullable(schema.string()), meta: schema.nullable(schema.object({}, { allowUnknowns: true })), query: schema.nullable(schema.string()), filters: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index f4a8263da6ba42..ff0f2a8782cd21 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -44,6 +44,7 @@ export interface RuleAlertParams { tags: string[]; to: string; timelineId: string | undefined | null; + timelineTitle: string | undefined | null; threats: ThreatParams[] | undefined | null; type: 'query' | 'saved_query'; version: number; @@ -60,6 +61,7 @@ export type RuleAlertParamsRest = Omit< | 'savedId' | 'riskScore' | 'timelineId' + | 'timelineTitle' | 'outputIndex' | 'updatedAt' | 'createdAt' @@ -68,6 +70,7 @@ export type RuleAlertParamsRest = Omit< false_positives: RuleAlertParams['falsePositives']; saved_id: RuleAlertParams['savedId']; timeline_id: RuleAlertParams['timelineId']; + timeline_title: RuleAlertParams['timelineTitle']; max_signals: RuleAlertParams['maxSignals']; risk_score: RuleAlertParams['riskScore']; output_index: RuleAlertParams['outputIndex']; diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts index fa8ae641686737..fbd39db6969bd4 100644 --- a/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts +++ b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts @@ -6,6 +6,12 @@ import { ManagementService } from '.'; +const mockSections = { + getSection: jest.fn(), + getAllSections: jest.fn(), + navigateToApp: jest.fn(), +}; + describe('ManagementService', () => { describe('#start', () => { it('registers the spaces management page under the kibana section', () => { @@ -18,6 +24,7 @@ describe('ManagementService', () => { legacy: { getSection: jest.fn().mockReturnValue(mockKibanaSection), }, + sections: mockSections, }; const deps = { @@ -49,6 +56,7 @@ describe('ManagementService', () => { legacy: { getSection: jest.fn().mockReturnValue(mockKibanaSection), }, + sections: mockSections, }; const deps = { @@ -66,6 +74,7 @@ describe('ManagementService', () => { legacy: { getSection: jest.fn().mockReturnValue(undefined), }, + sections: mockSections, }; const deps = { @@ -94,6 +103,7 @@ describe('ManagementService', () => { legacy: { getSection: jest.fn().mockReturnValue(mockKibanaSection), }, + sections: mockSections, }; const deps = { diff --git a/x-pack/legacy/plugins/transform/public/shim.ts b/x-pack/legacy/plugins/transform/public/shim.ts index f8edc752c9a215..d739dd2edddcc8 100644 --- a/x-pack/legacy/plugins/transform/public/shim.ts +++ b/x-pack/legacy/plugins/transform/public/shim.ts @@ -47,6 +47,7 @@ export interface Core extends npCore { esDocBasePath: string; esPluginDocBasePath: string; esStackOverviewDocBasePath: string; + esMLDocBasePath: string; }; docTitle: { change: typeof docTitle.change; @@ -93,6 +94,7 @@ export function createPublicShim(): { core: Core; plugins: Plugins } { esDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`, esPluginDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`, esStackOverviewDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack-overview/${DOC_LINK_VERSION}/`, + esMLDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/`, }, docTitle: { change: docTitle.change, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx index b5dcfce032724b..775e8c0c06aa50 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx @@ -66,32 +66,32 @@ export const DurationChart = ({ - - - - getTickFormat(d)} - title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { - defaultMessage: 'Duration ms', - })} - /> - {hasLines ? ( + {hasLines ? ( + + + + getTickFormat(d)} + title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { + defaultMessage: 'Duration ms', + })} + /> - ) : ( - - )} - + + ) : ( + + )} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx index aca43f550aa146..5c606f2356dfca 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx @@ -8,11 +8,12 @@ import { MonitorSummary, Check } from '../../../../../../common/graphql/types'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { MonitorListDrawerComponent } from '../monitor_list_drawer'; +import { MonitorDetails } from '../../../../../../common/runtime_types'; describe('MonitorListDrawer component', () => { let summary: MonitorSummary; let loadMonitorDetails: any; - let monitorDetails: any; + let monitorDetails: MonitorDetails; beforeEach(() => { summary = { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx index d793e60dcd089b..35b649fa357953 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx @@ -6,7 +6,6 @@ import React, { useEffect } from 'react'; import { EuiLink, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { get } from 'lodash'; import styled from 'styled-components'; import { connect } from 'react-redux'; import { MonitorSummary } from '../../../../../common/graphql/types'; @@ -16,6 +15,8 @@ import { MostRecentError } from './most_recent_error'; import { getMonitorDetails } from '../../../../state/selectors'; import { MonitorStatusList } from './monitor_status_list'; import { MonitorDetails } from '../../../../../common/runtime_types'; +import { useUrlParams } from '../../../../hooks'; +import { MonitorDetailsActionPayload } from '../../../../state/actions/types'; import { MonitorListActionsPopover } from '../monitor_list_actions_popover'; const ContainerDiv = styled.div` @@ -50,19 +51,20 @@ export function MonitorListDrawerComponent({ monitorDetails, }: MonitorListDrawerProps) { const monitorId = summary?.monitor_id; - useEffect(() => { - if (monitorId) { - loadMonitorDetails(monitorId); - } - }, [loadMonitorDetails, monitorId]); + const [getUrlParams] = useUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); - if (!summary || !summary.state.checks) { - return null; - } + useEffect(() => { + loadMonitorDetails({ + dateStart, + dateEnd, + monitorId, + }); + }, [dateStart, dateEnd, monitorId, loadMonitorDetails]); - const monitorUrl: string | undefined = get(summary.state.url, 'full', undefined); + const monitorUrl = summary?.state?.url?.full || ''; - return ( + return summary && summary.state.checks ? ( @@ -87,7 +89,7 @@ export function MonitorListDrawerComponent({ /> )} - ); + ) : null; } const mapStateToProps = (state: AppState, { summary }: any) => ({ @@ -95,7 +97,8 @@ const mapStateToProps = (state: AppState, { summary }: any) => ({ }); const mapDispatchToProps = (dispatch: any) => ({ - loadMonitorDetails: (monitorId: string) => dispatch(fetchMonitorDetails(monitorId)), + loadMonitorDetails: (actionPayload: MonitorDetailsActionPayload) => + dispatch(fetchMonitorDetails(actionPayload)), }); export const MonitorListDrawer = connect( diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts index eb9b475a35716e..f2b586b27dba69 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts @@ -16,5 +16,6 @@ export const useIndexPattern = (setIndexPattern: Dispatch) => { setIndexPattern(await fetch('/api/uptime/index_pattern', { method: 'GET' })); } getIndexPattern(); - }, [core.services.http, setIndexPattern]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [core.services.http]); }; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts index 99855bb8c8df31..cf4525a08e43cd 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { MonitorDetailsActionPayload } from './types'; +import { MonitorError } from '../../../common/runtime_types'; import { MonitorLocations } from '../../../common/runtime_types'; import { QueryParams } from './types'; @@ -17,12 +19,12 @@ export const FETCH_MONITOR_LOCATIONS_FAIL = 'FETCH_MONITOR_LOCATIONS_FAIL'; export interface MonitorDetailsState { monitorId: string; - error: Error; + error: MonitorError; } interface GetMonitorDetailsAction { type: typeof FETCH_MONITOR_DETAILS; - payload: string; + payload: MonitorDetailsActionPayload; } interface GetMonitorDetailsSuccessAction { @@ -54,10 +56,10 @@ interface GetMonitorLocationsFailAction { payload: any; } -export function fetchMonitorDetails(monitorId: string): GetMonitorDetailsAction { +export function fetchMonitorDetails(payload: MonitorDetailsActionPayload): GetMonitorDetailsAction { return { type: FETCH_MONITOR_DETAILS, - payload: monitorId, + payload, }; } diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts index 7ec288583f9feb..8419aaadc74bdd 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts @@ -10,3 +10,10 @@ export interface QueryParams { filters?: string; statusFilter?: string; } + +export interface MonitorDetailsActionPayload { + monitorId: string; + dateStart: string; + dateEnd: string; + location?: string; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts index 0bb2d8447419b4..fb38599495d845 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts @@ -3,53 +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. */ - -export const SET_INTEGRATION_POPOVER_STATE = 'SET_INTEGRATION_POPOVER_STATE'; -export const SET_BASE_PATH = 'SET_BASE_PATH'; -export const REFRESH_APP = 'REFRESH_APP'; +import { createAction } from 'redux-actions'; export interface PopoverState { id: string; open: boolean; } -interface SetBasePathAction { - type: typeof SET_BASE_PATH; - payload: string; -} - -interface SetIntegrationPopoverAction { - type: typeof SET_INTEGRATION_POPOVER_STATE; - payload: PopoverState; -} - -interface TriggerAppRefreshAction { - type: typeof REFRESH_APP; - payload: number; -} +export type UiPayload = PopoverState & string & number & Map; -export type UiActionTypes = - | SetIntegrationPopoverAction - | SetBasePathAction - | TriggerAppRefreshAction; +export const setBasePath = createAction('SET BASE PATH'); -export function toggleIntegrationsPopover(popoverState: PopoverState): SetIntegrationPopoverAction { - return { - type: SET_INTEGRATION_POPOVER_STATE, - payload: popoverState, - }; -} +export const triggerAppRefresh = createAction('REFRESH APP'); -export function setBasePath(basePath: string): SetBasePathAction { - return { - type: SET_BASE_PATH, - payload: basePath, - }; -} - -export function triggerAppRefresh(refreshTime: number): TriggerAppRefreshAction { - return { - type: REFRESH_APP, - payload: refreshTime, - }; -} +export const toggleIntegrationsPopover = createAction( + 'TOGGLE INTEGRATION POPOVER STATE' +); diff --git a/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts index 0fb00b935342e6..8b1220830f091b 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts @@ -6,6 +6,7 @@ import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; import { getApiPath } from '../../lib/helper'; +import { BaseParams } from './types'; import { MonitorDetailsType, MonitorDetails, @@ -19,12 +20,23 @@ interface ApiRequest { basePath: string; } +export type MonitorQueryParams = BaseParams & ApiRequest; + export const fetchMonitorDetails = async ({ monitorId, basePath, -}: ApiRequest): Promise => { - const url = getApiPath(`/api/uptime/monitor/details?monitorId=${monitorId}`, basePath); - const response = await fetch(url); + dateStart, + dateEnd, +}: MonitorQueryParams): Promise => { + const url = getApiPath(`/api/uptime/monitor/details`, basePath); + const params = { + monitorId, + dateStart, + dateEnd, + }; + const urlParams = new URLSearchParams(params).toString(); + const response = await fetch(`${url}?${urlParams}`); + if (!response.ok) { throw new Error(response.statusText); } diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/is_resizing.tsx b/x-pack/legacy/plugins/uptime/public/state/api/types.ts similarity index 55% rename from x-pack/legacy/plugins/siem/public/components/resize_handle/is_resizing.tsx rename to x-pack/legacy/plugins/uptime/public/state/api/types.ts index 5eb2d397b4c989..278cfce29986f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/is_resizing.tsx +++ b/x-pack/legacy/plugins/uptime/public/state/api/types.ts @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState } from 'react'; - -export const useIsContainerResizing = () => { - const [isResizing, setIsResizing] = useState(false); - - return { - isResizing, - setIsResizing, - }; -}; +export interface BaseParams { + basePath: string; + dateStart: string; + dateEnd: string; + filters?: string; + statusFilter?: string; + location?: string; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts index 210004bb343bbe..1cac7424b4e5ba 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts @@ -16,12 +16,18 @@ import { } from '../actions/monitor'; import { fetchMonitorDetails, fetchMonitorLocations } from '../api'; import { getBasePath } from '../selectors'; +import { MonitorDetailsActionPayload } from '../actions/types'; function* monitorDetailsEffect(action: Action) { - const monitorId: string = action.payload; + const { monitorId, dateStart, dateEnd }: MonitorDetailsActionPayload = action.payload; try { const basePath = yield select(getBasePath); - const response = yield call(fetchMonitorDetails, { monitorId, basePath }); + const response = yield call(fetchMonitorDetails, { + monitorId, + basePath, + dateStart, + dateEnd, + }); yield put({ type: FETCH_MONITOR_DETAILS_SUCCESS, payload: response }); } catch (error) { yield put({ type: FETCH_MONITOR_DETAILS_FAIL, payload: error.message }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap index 75516da18c6336..155f7edbcbf33c 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap @@ -21,7 +21,7 @@ Object { exports[`ui reducer updates the refresh value 1`] = ` Object { - "basePath": "", + "basePath": "abc", "integrationsPopoverOpen": null, "lastRefresh": 125, } diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts index 9be863f0b700d5..ff9b7c3f9e8a47 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UiActionTypes } from '../../actions'; +import { setBasePath, toggleIntegrationsPopover, triggerAppRefresh } from '../../actions'; import { uiReducer } from '../ui'; +import { Action } from 'redux-actions'; describe('ui reducer', () => { it(`sets the application's base path`, () => { - const action: UiActionTypes = { - type: 'SET_BASE_PATH', - payload: 'yyz', - }; + const action = setBasePath('yyz') as Action; expect( uiReducer( { @@ -26,13 +24,10 @@ describe('ui reducer', () => { }); it('adds integration popover status to state', () => { - const action: UiActionTypes = { - type: 'SET_INTEGRATION_POPOVER_STATE', - payload: { - id: 'popover-2', - open: true, - }, - }; + const action = toggleIntegrationsPopover({ + id: 'popover-2', + open: true, + }) as Action; expect( uiReducer( { @@ -46,10 +41,16 @@ describe('ui reducer', () => { }); it('updates the refresh value', () => { - const action: UiActionTypes = { - type: 'REFRESH_APP', - payload: 125, - }; - expect(uiReducer(undefined, action)).toMatchSnapshot(); + const action = triggerAppRefresh(125) as Action; + expect( + uiReducer( + { + basePath: 'abc', + integrationsPopoverOpen: null, + lastRefresh: 125, + }, + action + ) + ).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts index f0c3d1c2cbecf6..b588feaf8a75a6 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts @@ -12,5 +12,6 @@ import { uiReducer } from './ui'; export const rootReducer = combineReducers({ monitor: monitorReducer, snapshot: snapshotReducer, + // @ts-ignore for now TODO: refactor to use redux-action ui: uiReducer, }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts index be95c8fff6bec2..b23245aa65fcaa 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { handleActions, Action } from 'redux-actions'; import { - UiActionTypes, PopoverState, - SET_INTEGRATION_POPOVER_STATE, - SET_BASE_PATH, - REFRESH_APP, + toggleIntegrationsPopover, + setBasePath, + triggerAppRefresh, + UiPayload, } from '../actions/ui'; export interface UiState { @@ -24,29 +25,22 @@ const initialState: UiState = { lastRefresh: Date.now(), }; -export function uiReducer(state = initialState, action: UiActionTypes): UiState { - switch (action.type) { - case REFRESH_APP: - return { - ...state, - lastRefresh: action.payload, - }; - case SET_INTEGRATION_POPOVER_STATE: - const popoverState = action.payload; - return { - ...state, - integrationsPopoverOpen: { - id: popoverState.id, - open: popoverState.open, - }, - }; - case SET_BASE_PATH: - const basePath = action.payload; - return { - ...state, - basePath, - }; - default: - return state; - } -} +export const uiReducer = handleActions( + { + [String(toggleIntegrationsPopover)]: (state, action: Action) => ({ + ...state, + integrationsPopoverOpen: action.payload as PopoverState, + }), + + [String(setBasePath)]: (state, action: Action) => ({ + ...state, + basePath: action.payload as string, + }), + + [String(triggerAppRefresh)]: (state, action: Action) => ({ + ...state, + lastRefresh: action.payload as number, + }), + }, + initialState +); diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts index b61ed836634354..33b6400c56c608 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -25,7 +25,11 @@ describe('state selectors', () => { errors: [], loading: false, }, - ui: { basePath: 'yyz', integrationsPopoverOpen: null, lastRefresh: 125 }, + ui: { + basePath: 'yyz', + integrationsPopoverOpen: null, + lastRefresh: 125, + }, }; it('selects base path from state', () => { diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 1792c84c45220e..08d1e8c8ca36d7 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -6,11 +6,13 @@ import { AppState } from '../../state'; +// UI Selectors export const getBasePath = ({ ui: { basePath } }: AppState) => basePath; export const isIntegrationsPopupOpen = ({ ui: { integrationsPopoverOpen } }: AppState) => integrationsPopoverOpen; +// Monitor Selectors export const getMonitorDetails = (state: AppState, summary: any) => { return state.monitor.monitorDetailsList[summary.monitor_id]; }; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts index b3d8cb855d55a1..99d346e5d666eb 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts @@ -27,6 +27,8 @@ export interface GetFilterBarParams { export interface GetMonitorDetailsParams { monitorId: string; + dateStart: string; + dateEnd: string; } export interface GetMonitorPageTitleParams { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index b3352054589659..5f4c2e45d97597 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -231,7 +231,23 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { }; }, - getMonitorDetails: async ({ callES, monitorId }) => { + getMonitorDetails: async ({ callES, monitorId, dateStart, dateEnd }) => { + const queryFilters: any = [ + { + range: { + '@timestamp': { + gte: dateStart, + lte: dateEnd, + }, + }, + }, + { + term: { + 'monitor.id': monitorId, + }, + }, + ]; + const params = { index: INDEX_NAMES.HEARTBEAT, body: { @@ -246,13 +262,7 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { }, }, ], - filter: [ - { - term: { - 'monitor.id': monitorId, - }, - }, - ], + filter: queryFilters, }, }, sort: [ diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts index 6862bed8d2bdd5..d05cb5758ca364 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts @@ -157,14 +157,14 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = { statusFilter, }) => { const boolFilters = parseFilterQuery(filters); - const additionaFilters = []; + const additionalFilters = []; if (monitorId) { - additionaFilters.push({ match: { 'monitor.id': monitorId } }); + additionalFilters.push({ match: { 'monitor.id': monitorId } }); } if (boolFilters) { - additionaFilters.push(boolFilters); + additionalFilters.push(boolFilters); } - const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionaFilters); + const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionalFilters); const interval = getHistogramInterval(dateRangeStart, dateRangeEnd); const intervalFormatted = getHistogramIntervalFormatted(dateRangeStart, dateRangeEnd); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts index 5259aa1d617116..c81fec933cb227 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts @@ -4,14 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const getRange = (dateRangeStart: string, dateRangeEnd: string) => ({ - range: { - '@timestamp': { - gte: dateRangeStart, - lte: dateRangeEnd, - }, - }, -}); +import { makeDateRangeFilter } from './make_date_rate_filter'; export const getFilterClause = ( dateRangeStart: string, @@ -19,5 +12,5 @@ export const getFilterClause = ( additionalKeys?: Array<{ [key: string]: any }> ) => additionalKeys && additionalKeys.length > 0 - ? [getRange(dateRangeStart, dateRangeEnd), ...additionalKeys] - : [getRange(dateRangeStart, dateRangeEnd)]; + ? [makeDateRangeFilter(dateRangeStart, dateRangeEnd), ...additionalKeys] + : [makeDateRangeFilter(dateRangeStart, dateRangeEnd)]; diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts index a57e5ec469c59d..9e1bc6f0d6a96b 100644 --- a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts +++ b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts @@ -13,18 +13,24 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ path: '/api/uptime/monitor/details', validate: { query: schema.object({ - monitorId: schema.maybe(schema.string()), + monitorId: schema.string(), + dateStart: schema.maybe(schema.string()), + dateEnd: schema.maybe(schema.string()), }), }, options: { tags: ['access:uptime'], }, handler: async ({ callES }, _context, request, response): Promise => { - const { monitorId } = request.query; - + const { monitorId, dateStart, dateEnd } = request.query; return response.ok({ body: { - ...(await libs.monitors.getMonitorDetails({ callES, monitorId })), + ...(await libs.monitors.getMonitorDetails({ + callES, + monitorId, + dateStart, + dateEnd, + })), }, }); }, diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts index 2e8c81efa19c01..a311c31082183c 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.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 { first } from 'rxjs/operators'; import { Plugin, CoreSetup } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { PLUGIN } from '../../common/constants'; @@ -23,7 +22,7 @@ export class WatcherServerPlugin implements Plugin { { http, elasticsearch: elasticsearchService }: CoreSetup, { __LEGACY: serverShim }: { __LEGACY: ServerShim } ) { - const elasticsearch = await elasticsearchService.adminClient$.pipe(first()).toPromise(); + const elasticsearch = await elasticsearchService.adminClient; const router = http.createRouter(); const routeDependencies: RouteDependencies = { elasticsearch, diff --git a/x-pack/package.json b/x-pack/package.json index 110db56c5d4ed4..ffa593f5728ee3 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -96,7 +96,7 @@ "@types/react-test-renderer": "^16.9.1", "@types/recompose": "^0.30.6", "@types/reduce-reducers": "^0.3.0", - "@types/redux-actions": "^2.2.1", + "@types/redux-actions": "^2.6.1", "@types/sinon": "^7.0.13", "@types/styled-components": "^4.4.1", "@types/supertest": "^2.0.5", @@ -313,7 +313,7 @@ "recompose": "^0.26.0", "reduce-reducers": "^0.4.3", "redux": "4.0.0", - "redux-actions": "2.2.1", + "redux-actions": "2.6.5", "redux-observable": "^1.0.0", "redux-saga": "^0.16.0", "redux-thunk": "2.3.0", @@ -321,6 +321,7 @@ "request": "^2.88.0", "reselect": "3.0.1", "resize-observer-polyfill": "^1.5.0", + "re-resizable": "^6.1.1", "rison-node": "0.3.1", "rxjs": "^6.5.3", "semver": "5.7.0", diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index a1cf2ae4e8ead8..11d91efdf6b01d 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -58,16 +58,14 @@ export class APMPlugin implements Plugin { }); await new Promise(resolve => { - combineLatest(mergedConfig$, core.elasticsearch.dataClient$).subscribe( - async ([config, dataClient]) => { - this.currentConfig = config; - await createApmAgentConfigurationIndex({ - esClient: dataClient, - config, - }); - resolve(); - } - ); + mergedConfig$.subscribe(async config => { + this.currentConfig = config; + await createApmAgentConfigurationIndex({ + esClient: core.elasticsearch.dataClient, + config, + }); + resolve(); + }); }); return { diff --git a/x-pack/plugins/case/README.md b/x-pack/plugins/case/README.md new file mode 100644 index 00000000000000..c0acb878352079 --- /dev/null +++ b/x-pack/plugins/case/README.md @@ -0,0 +1,9 @@ +# Case Workflow + +*Experimental Feature* + +Elastic is developing a Case Management Workflow. Follow our progress: + +- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest) +- [Github Meta](https://github.com/elastic/kibana/issues/50103) + diff --git a/x-pack/plugins/case/kibana.json b/x-pack/plugins/case/kibana.json new file mode 100644 index 00000000000000..23e3cc789ad3b3 --- /dev/null +++ b/x-pack/plugins/case/kibana.json @@ -0,0 +1,9 @@ +{ + "configPath": ["xpack", "case"], + "id": "case", + "kibanaVersion": "kibana", + "requiredPlugins": ["security"], + "server": true, + "ui": false, + "version": "8.0.0" +} diff --git a/x-pack/plugins/case/server/config.ts b/x-pack/plugins/case/server/config.ts new file mode 100644 index 00000000000000..a7cb117198f9b7 --- /dev/null +++ b/x-pack/plugins/case/server/config.ts @@ -0,0 +1,15 @@ +/* + * 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: false }), + indexPattern: schema.string({ defaultValue: '.case-test-2' }), + secret: schema.string({ defaultValue: 'Cool secret huh?' }), +}); + +export type ConfigType = TypeOf; diff --git a/x-pack/plugins/case/server/constants.ts b/x-pack/plugins/case/server/constants.ts new file mode 100644 index 00000000000000..276dcd135254a3 --- /dev/null +++ b/x-pack/plugins/case/server/constants.ts @@ -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 const CASE_SAVED_OBJECT = 'case-workflow'; +export const CASE_COMMENT_SAVED_OBJECT = 'case-workflow-comment'; diff --git a/x-pack/plugins/case/server/index.ts b/x-pack/plugins/case/server/index.ts new file mode 100644 index 00000000000000..3963debea97955 --- /dev/null +++ b/x-pack/plugins/case/server/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 { PluginInitializerContext } from '../../../../src/core/server'; +import { ConfigSchema } from './config'; +import { CasePlugin } from './plugin'; +export { NewCaseFormatted, NewCommentFormatted } from './routes/api/types'; + +export const config = { schema: ConfigSchema }; +export const plugin = (initializerContext: PluginInitializerContext) => + new CasePlugin(initializerContext); diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts new file mode 100644 index 00000000000000..c52461cade0586 --- /dev/null +++ b/x-pack/plugins/case/server/plugin.ts @@ -0,0 +1,63 @@ +/* + * 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 { first, map } from 'rxjs/operators'; +import { CoreSetup, Logger, PluginInitializerContext } from 'kibana/server'; +import { ConfigType } from './config'; +import { initCaseApi } from './routes/api'; +import { CaseService } from './services'; +import { PluginSetupContract as SecurityPluginSetup } from '../../security/server'; + +function createConfig$(context: PluginInitializerContext) { + return context.config.create().pipe(map(config => config)); +} + +export interface PluginsSetup { + security: SecurityPluginSetup; +} + +export class CasePlugin { + private readonly log: Logger; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.log = this.initializerContext.logger.get(); + } + + public async setup(core: CoreSetup, plugins: PluginsSetup) { + const config = await createConfig$(this.initializerContext) + .pipe(first()) + .toPromise(); + + if (!config.enabled) { + return; + } + const service = new CaseService(this.log); + + this.log.debug( + `Setting up Case Workflow with core contract [${Object.keys( + core + )}] and plugins [${Object.keys(plugins)}]` + ); + + const caseService = await service.setup({ + authentication: plugins.security.authc, + }); + + const router = core.http.createRouter(); + initCaseApi({ + caseService, + router, + }); + } + + public start() { + this.log.debug(`Starting Case Workflow`); + } + + public stop() { + this.log.debug(`Stopping Case Workflow`); + } +} diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts new file mode 100644 index 00000000000000..94ce9627b9ac64 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts @@ -0,0 +1,35 @@ +/* + * 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 { Authentication } from '../../../../../security/server'; + +const getCurrentUser = jest.fn().mockReturnValue({ + username: 'awesome', + full_name: 'Awesome D00d', +}); +const getCurrentUserThrow = jest.fn().mockImplementation(() => { + throw new Error('Bad User - the user is not authenticated'); +}); + +export const authenticationMock = { + create: (): jest.Mocked => ({ + login: jest.fn(), + createAPIKey: jest.fn(), + getCurrentUser, + invalidateAPIKey: jest.fn(), + isAuthenticated: jest.fn(), + logout: jest.fn(), + getSessionInfo: jest.fn(), + }), + createInvalid: (): jest.Mocked => ({ + login: jest.fn(), + createAPIKey: jest.fn(), + getCurrentUser: getCurrentUserThrow, + invalidateAPIKey: jest.fn(), + isAuthenticated: jest.fn(), + logout: jest.fn(), + getSessionInfo: jest.fn(), + }), +}; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts new file mode 100644 index 00000000000000..360c6de67b2a81 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -0,0 +1,77 @@ +/* + * 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 { SavedObjectsClientContract, SavedObjectsErrorHelpers } from 'src/core/server'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../constants'; + +export const createMockSavedObjectsRepository = (savedObject: any[] = []) => { + const mockSavedObjectsClientContract = ({ + get: jest.fn((type, id) => { + const result = savedObject.filter(s => s.id === id); + if (!result.length) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + return result[0]; + }), + find: jest.fn(findArgs => { + if (findArgs.hasReference && findArgs.hasReference.id === 'bad-guy') { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + return { + total: savedObject.length, + saved_objects: savedObject, + }; + }), + create: jest.fn((type, attributes, references) => { + if (attributes.description === 'Throw an error' || attributes.comment === 'Throw an error') { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + if (type === CASE_COMMENT_SAVED_OBJECT) { + return { + type, + id: 'mock-comment', + attributes, + ...references, + updated_at: '2019-12-02T22:48:08.327Z', + version: 'WzksMV0=', + }; + } + return { + type, + id: 'mock-it', + attributes, + references: [], + updated_at: '2019-12-02T22:48:08.327Z', + version: 'WzksMV0=', + }; + }), + update: jest.fn((type, id, attributes) => { + if (!savedObject.find(s => s.id === id)) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + return { + id, + type, + updated_at: '2019-11-22T22:50:55.191Z', + version: 'WzE3LDFd', + attributes, + }; + }), + delete: jest.fn((type: string, id: string) => { + const result = savedObject.filter(s => s.id === id); + if (!result.length) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + if (type === 'case-workflow-comment' && id === 'bad-guy') { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + return {}; + }), + deleteByNamespace: jest.fn(), + } as unknown) as jest.Mocked; + + return mockSavedObjectsClientContract; +}; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts new file mode 100644 index 00000000000000..e1fec2d6b229ce --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { mockCases, mockCasesErrorTriggerData, mockCaseComments } from './mock_saved_objects'; +export { createMockSavedObjectsRepository } from './create_mock_so_repository'; +export { createRouteContext } from './route_contexts'; +export { authenticationMock } from './authc_mock'; +export { createRoute } from './mock_router'; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts new file mode 100644 index 00000000000000..84889c3ac49be1 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; +import { loggingServiceMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; +import { CaseService } from '../../../services'; +import { authenticationMock } from '../__fixtures__'; +import { RouteDeps } from '../index'; + +export const createRoute = async ( + api: (deps: RouteDeps) => void, + method: 'get' | 'post' | 'delete', + badAuth = false +) => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + + const log = loggingServiceMock.create().get('case'); + + const service = new CaseService(log); + const caseService = await service.setup({ + authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(), + }); + + api({ + router, + caseService, + }); + + return router[method].mock.calls[0][1]; +}; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts new file mode 100644 index 00000000000000..d59f0977e69934 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -0,0 +1,143 @@ +/* + * 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 mockCases = [ + { + type: 'case-workflow', + id: 'mock-id-1', + attributes: { + created_at: 1574718888885, + created_by: { + full_name: null, + username: 'elastic', + }, + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['defacement'], + case_type: 'security', + assignees: [], + }, + references: [], + updated_at: '2019-11-25T21:54:48.952Z', + version: 'WzAsMV0=', + }, + { + type: 'case-workflow', + id: 'mock-id-2', + attributes: { + created_at: 1574721120834, + created_by: { + full_name: null, + username: 'elastic', + }, + description: 'Oh no, a bad meanie destroying data!', + title: 'Damaging Data Destruction Detected', + state: 'open', + tags: ['Data Destruction'], + case_type: 'security', + assignees: [], + }, + references: [], + updated_at: '2019-11-25T22:32:00.900Z', + version: 'WzQsMV0=', + }, + { + type: 'case-workflow', + id: 'mock-id-3', + attributes: { + created_at: 1574721137881, + created_by: { + full_name: null, + username: 'elastic', + }, + description: 'Oh no, a bad meanie going LOLBins all over the place!', + title: 'Another bad one', + state: 'open', + tags: ['LOLBins'], + case_type: 'security', + assignees: [], + }, + references: [], + updated_at: '2019-11-25T22:32:17.947Z', + version: 'WzUsMV0=', + }, +]; + +export const mockCasesErrorTriggerData = [ + { + id: 'valid-id', + }, + { + id: 'bad-guy', + }, +]; + +export const mockCaseComments = [ + { + type: 'case-workflow-comment', + id: 'mock-comment-1', + attributes: { + comment: 'Wow, good luck catching that bad meanie!', + created_at: 1574718900112, + created_by: { + full_name: null, + username: 'elastic', + }, + }, + references: [ + { + type: 'case-workflow', + name: 'associated-case-workflow', + id: 'mock-id-1', + }, + ], + updated_at: '2019-11-25T21:55:00.177Z', + version: 'WzEsMV0=', + }, + { + type: 'case-workflow-comment', + id: 'mock-comment-2', + attributes: { + comment: 'Well I decided to update my comment. So what? Deal with it.', + created_at: 1574718902724, + created_by: { + full_name: null, + username: 'elastic', + }, + }, + references: [ + { + type: 'case-workflow', + name: 'associated-case-workflow', + id: 'mock-id-1', + }, + ], + updated_at: '2019-11-25T21:55:14.633Z', + version: 'WzMsMV0=', + }, + { + type: 'case-workflow-comment', + id: 'mock-comment-3', + attributes: { + comment: 'Wow, good luck catching that bad meanie!', + created_at: 1574721150542, + created_by: { + full_name: null, + username: 'elastic', + }, + }, + references: [ + { + type: 'case-workflow', + name: 'associated-case-workflow', + id: 'mock-id-3', + }, + ], + updated_at: '2019-11-25T22:32:30.608Z', + version: 'WzYsMV0=', + }, +]; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts new file mode 100644 index 00000000000000..b1881e394e796a --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.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 { RequestHandlerContext } from 'src/core/server'; + +export const createRouteContext = (client: any) => { + return ({ + core: { + savedObjects: { + client, + }, + }, + } as unknown) as RequestHandlerContext; +}; diff --git a/x-pack/plugins/case/server/routes/api/__tests__/delete_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/delete_case.test.ts new file mode 100644 index 00000000000000..9ea42ba42406b1 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/delete_case.test.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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, + mockCasesErrorTriggerData, +} from '../__fixtures__'; +import { initDeleteCaseApi } from '../delete_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('DELETE case', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initDeleteCaseApi, 'delete'); + }); + it(`deletes the case. responds with 204`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'mock-id-1', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(204); + }); + it(`returns an error when thrown from deleteCase service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'not-real', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + }); + it(`returns an error when thrown from getAllCaseComments service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'bad-guy', + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); + it(`returns an error when thrown from deleteComment service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'valid-id', + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/delete_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/delete_comment.test.ts new file mode 100644 index 00000000000000..e50b3cbaa9c9a1 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/delete_comment.test.ts @@ -0,0 +1,53 @@ +/* + * 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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, + mockCasesErrorTriggerData, +} from '../__fixtures__'; +import { initDeleteCommentApi } from '../delete_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('DELETE comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initDeleteCommentApi, 'delete'); + }); + it(`deletes the comment. responds with 204`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{comment_id}', + method: 'delete', + params: { + comment_id: 'mock-id-1', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(204); + }); + it(`returns an error when thrown from deleteComment service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{comment_id}', + method: 'delete', + params: { + comment_id: 'bad-guy', + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/get_all_cases.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/get_all_cases.test.ts new file mode 100644 index 00000000000000..2f8a229c08f29c --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/get_all_cases.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initGetAllCasesApi } from '../get_all_cases'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('GET all cases', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetAllCasesApi, 'get'); + }); + it(`returns the case without case comments when includeComments is false`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'get', + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.saved_objects).toHaveLength(3); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/get_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/get_case.test.ts new file mode 100644 index 00000000000000..3c5f8e52d19466 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/get_case.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, + mockCasesErrorTriggerData, +} from '../__fixtures__'; +import { initGetCaseApi } from '../get_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('GET case', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetCaseApi, 'get'); + }); + it(`returns the case without case comments when includeComments is false`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'mock-id-1', + }, + method: 'get', + query: { + includeComments: false, + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(200); + expect(response.payload).toEqual(mockCases.find(s => s.id === 'mock-id-1')); + expect(response.payload.comments).toBeUndefined(); + }); + it(`returns an error when thrown from getCase`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'abcdefg', + }, + method: 'get', + query: { + includeComments: false, + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); + it(`returns the case with case comments when includeComments is true`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'mock-id-1', + }, + method: 'get', + query: { + includeComments: true, + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(200); + expect(response.payload.comments.saved_objects).toHaveLength(3); + }); + it(`returns an error when thrown from getAllCaseComments`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'bad-guy', + }, + method: 'get', + query: { + includeComments: true, + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(400); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/get_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/get_comment.test.ts new file mode 100644 index 00000000000000..9b6a1e435838bd --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/get_comment.test.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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCaseComments, +} from '../__fixtures__'; +import { initGetCommentApi } from '../get_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('GET comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetCommentApi, 'get'); + }); + it(`returns the comment`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{id}', + method: 'get', + params: { + id: 'mock-comment-1', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload).toEqual(mockCaseComments.find(s => s.id === 'mock-comment-1')); + }); + it(`returns an error when getComment throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{id}', + method: 'get', + params: { + id: 'not-real', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/post_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/post_case.test.ts new file mode 100644 index 00000000000000..bb688dde4c58f8 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/post_case.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initPostCaseApi } from '../post_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('POST cases', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initPostCaseApi, 'post'); + }); + it(`Posts a new case`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['defacement'], + case_type: 'security', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-it'); + expect(response.payload.attributes.created_by.username).toEqual('awesome'); + }); + it(`Returns an error if postNewCase throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'Throw an error', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['error'], + case_type: 'security', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + expect(response.payload.isBoom).toEqual(true); + }); + it(`Returns an error if user authentication throws`, async () => { + routeHandler = await createRoute(initPostCaseApi, 'post', true); + + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['defacement'], + case_type: 'security', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(500); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/post_comment.test.ts new file mode 100644 index 00000000000000..0c059b7f15ea48 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/post_comment.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initPostCommentApi } from '../post_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('POST comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initPostCommentApi, 'post'); + }); + it(`Posts a new comment`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + comment: 'Wow, good luck catching that bad meanie!', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-comment'); + expect(response.payload.references[0].id).toEqual('mock-id-1'); + }); + it(`Returns an error if the case does not exist`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'this-is-not-real', + }, + body: { + comment: 'Wow, good luck catching that bad meanie!', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); + it(`Returns an error if postNewCase throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + comment: 'Throw an error', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + expect(response.payload.isBoom).toEqual(true); + }); + it(`Returns an error if user authentication throws`, async () => { + routeHandler = await createRoute(initPostCommentApi, 'post', true); + + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + comment: 'Wow, good luck catching that bad meanie!', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(500); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts new file mode 100644 index 00000000000000..7ed478d2e7c019 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts @@ -0,0 +1,59 @@ +/* + * 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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initUpdateCaseApi } from '../update_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('UPDATE case', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initUpdateCaseApi, 'post'); + }); + it(`Updates a case`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + state: 'closed', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-id-1'); + expect(response.payload.attributes.state).toEqual('closed'); + }); + it(`Returns an error if updateCase throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'post', + params: { + id: 'mock-id-does-not-exist', + }, + body: { + state: 'closed', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts new file mode 100644 index 00000000000000..8aa84b45b7dbb2 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts @@ -0,0 +1,59 @@ +/* + * 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 { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCaseComments, +} from '../__fixtures__'; +import { initUpdateCommentApi } from '../update_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('UPDATE comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initUpdateCommentApi, 'post'); + }); + it(`Updates a comment`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comment/{id}', + method: 'post', + params: { + id: 'mock-comment-1', + }, + body: { + comment: 'Update my comment', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-comment-1'); + expect(response.payload.attributes.comment).toEqual('Update my comment'); + }); + it(`Returns an error if updateComment throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comment/{id}', + method: 'post', + params: { + id: 'mock-comment-does-not-exist', + }, + body: { + comment: 'Update my comment', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/delete_case.ts b/x-pack/plugins/case/server/routes/api/delete_case.ts new file mode 100644 index 00000000000000..a5ae72b8b46ff0 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/delete_case.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initDeleteCaseApi({ caseService, router }: RouteDeps) { + router.delete( + { + path: '/api/cases/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + let allCaseComments; + try { + await caseService.deleteCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + allCaseComments = await caseService.getAllCaseComments({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + if (allCaseComments.saved_objects.length > 0) { + await Promise.all( + allCaseComments.saved_objects.map(({ id }) => + caseService.deleteComment({ + client: context.core.savedObjects.client, + commentId: id, + }) + ) + ); + } + return response.noContent(); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/delete_comment.ts b/x-pack/plugins/case/server/routes/api/delete_comment.ts new file mode 100644 index 00000000000000..4a540dd9fd69ff --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/delete_comment.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initDeleteCommentApi({ caseService, router }: RouteDeps) { + router.delete( + { + path: '/api/cases/comments/{comment_id}', + validate: { + params: schema.object({ + comment_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + const client = context.core.savedObjects.client; + try { + await caseService.deleteComment({ + client, + commentId: request.params.comment_id, + }); + return response.noContent(); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_all_case_comments.ts b/x-pack/plugins/case/server/routes/api/get_all_case_comments.ts new file mode 100644 index 00000000000000..cc4956ead1bd78 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_all_case_comments.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetAllCaseCommentsApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/{id}/comments', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + try { + const theComments = await caseService.getAllCaseComments({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + return response.ok({ body: theComments }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_all_cases.ts b/x-pack/plugins/case/server/routes/api/get_all_cases.ts new file mode 100644 index 00000000000000..749a183dfe9807 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_all_cases.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetAllCasesApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases', + validate: false, + }, + async (context, request, response) => { + try { + const cases = await caseService.getAllCases({ + client: context.core.savedObjects.client, + }); + return response.ok({ body: cases }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_case.ts b/x-pack/plugins/case/server/routes/api/get_case.ts new file mode 100644 index 00000000000000..6aad22a1ebf1b6 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_case.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 { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetCaseApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + query: schema.object({ + includeComments: schema.string({ defaultValue: 'true' }), + }), + }, + }, + async (context, request, response) => { + let theCase; + const includeComments = JSON.parse(request.query.includeComments); + try { + theCase = await caseService.getCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + if (!includeComments) { + return response.ok({ body: theCase }); + } + try { + const theComments = await caseService.getAllCaseComments({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + return response.ok({ body: { ...theCase, comments: theComments } }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_comment.ts b/x-pack/plugins/case/server/routes/api/get_comment.ts new file mode 100644 index 00000000000000..6fd507d89738da --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_comment.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetCommentApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/comments/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + try { + const theComment = await caseService.getComment({ + client: context.core.savedObjects.client, + commentId: request.params.id, + }); + return response.ok({ body: theComment }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts new file mode 100644 index 00000000000000..11ef91d539e87b --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -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 { IRouter } from 'src/core/server'; +import { initDeleteCommentApi } from './delete_comment'; +import { initDeleteCaseApi } from './delete_case'; +import { initGetAllCaseCommentsApi } from './get_all_case_comments'; +import { initGetAllCasesApi } from './get_all_cases'; +import { initGetCaseApi } from './get_case'; +import { initGetCommentApi } from './get_comment'; +import { initPostCaseApi } from './post_case'; +import { initPostCommentApi } from './post_comment'; +import { initUpdateCaseApi } from './update_case'; +import { initUpdateCommentApi } from './update_comment'; +import { CaseServiceSetup } from '../../services'; + +export interface RouteDeps { + caseService: CaseServiceSetup; + router: IRouter; +} + +export function initCaseApi(deps: RouteDeps) { + initGetAllCaseCommentsApi(deps); + initGetAllCasesApi(deps); + initGetCaseApi(deps); + initGetCommentApi(deps); + initDeleteCaseApi(deps); + initDeleteCommentApi(deps); + initPostCaseApi(deps); + initPostCommentApi(deps); + initUpdateCaseApi(deps); + initUpdateCommentApi(deps); +} diff --git a/x-pack/plugins/case/server/routes/api/post_case.ts b/x-pack/plugins/case/server/routes/api/post_case.ts new file mode 100644 index 00000000000000..e5aa0a3548b48f --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/post_case.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 { formatNewCase, wrapError } from './utils'; +import { NewCaseSchema } from './schema'; +import { RouteDeps } from '.'; + +export function initPostCaseApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases', + validate: { + body: NewCaseSchema, + }, + }, + async (context, request, response) => { + let createdBy; + try { + createdBy = await caseService.getUser({ request, response }); + } catch (error) { + return response.customError(wrapError(error)); + } + + try { + const newCase = await caseService.postNewCase({ + client: context.core.savedObjects.client, + attributes: formatNewCase(request.body, { + ...createdBy, + }), + }); + return response.ok({ body: newCase }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/post_comment.ts b/x-pack/plugins/case/server/routes/api/post_comment.ts new file mode 100644 index 00000000000000..3f4592f5bb11f3 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/post_comment.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { formatNewComment, wrapError } from './utils'; +import { NewCommentSchema } from './schema'; +import { RouteDeps } from '.'; +import { CASE_SAVED_OBJECT } from '../../constants'; + +export function initPostCommentApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/{id}/comment', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: NewCommentSchema, + }, + }, + async (context, request, response) => { + let createdBy; + let newComment; + try { + await caseService.getCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + createdBy = await caseService.getUser({ request, response }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + newComment = await caseService.postNewComment({ + client: context.core.savedObjects.client, + attributes: formatNewComment({ + newComment: request.body, + ...createdBy, + }), + references: [ + { + type: CASE_SAVED_OBJECT, + name: `associated-${CASE_SAVED_OBJECT}`, + id: request.params.id, + }, + ], + }); + + return response.ok({ body: newComment }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/schema.ts b/x-pack/plugins/case/server/routes/api/schema.ts new file mode 100644 index 00000000000000..4a4a0c3a11e362 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/schema.ts @@ -0,0 +1,44 @@ +/* + * 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 UserSchema = schema.object({ + username: schema.string(), + full_name: schema.maybe(schema.string()), +}); + +export const NewCommentSchema = schema.object({ + comment: schema.string(), +}); + +export const CommentSchema = schema.object({ + comment: schema.string(), + created_at: schema.number(), + created_by: UserSchema, +}); + +export const UpdatedCommentSchema = schema.object({ + comment: schema.string(), +}); + +export const NewCaseSchema = schema.object({ + assignees: schema.arrayOf(UserSchema, { defaultValue: [] }), + description: schema.string(), + title: schema.string(), + state: schema.oneOf([schema.literal('open'), schema.literal('closed')], { defaultValue: 'open' }), + tags: schema.arrayOf(schema.string(), { defaultValue: [] }), + case_type: schema.string(), +}); + +export const UpdatedCaseSchema = schema.object({ + assignees: schema.maybe(schema.arrayOf(UserSchema)), + description: schema.maybe(schema.string()), + title: schema.maybe(schema.string()), + state: schema.maybe(schema.oneOf([schema.literal('open'), schema.literal('closed')])), + tags: schema.maybe(schema.arrayOf(schema.string())), + case_type: schema.maybe(schema.string()), +}); diff --git a/x-pack/plugins/case/server/routes/api/types.ts b/x-pack/plugins/case/server/routes/api/types.ts new file mode 100644 index 00000000000000..d943e4e5fd7dd7 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/types.ts @@ -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 { TypeOf } from '@kbn/config-schema'; +import { + CommentSchema, + NewCaseSchema, + NewCommentSchema, + UpdatedCaseSchema, + UpdatedCommentSchema, + UserSchema, +} from './schema'; + +export type NewCaseType = TypeOf; +export type NewCommentFormatted = TypeOf; +export type NewCommentType = TypeOf; +export type UpdatedCaseTyped = TypeOf; +export type UpdatedCommentType = TypeOf; +export type UserType = TypeOf; + +export interface NewCaseFormatted extends NewCaseType { + created_at: number; + created_by: UserType; +} + +export interface UpdatedCaseType { + assignees?: UpdatedCaseTyped['assignees']; + description?: UpdatedCaseTyped['description']; + title?: UpdatedCaseTyped['title']; + state?: UpdatedCaseTyped['state']; + tags?: UpdatedCaseTyped['tags']; + case_type?: UpdatedCaseTyped['case_type']; +} diff --git a/x-pack/plugins/case/server/routes/api/update_case.ts b/x-pack/plugins/case/server/routes/api/update_case.ts new file mode 100644 index 00000000000000..52c8cab0022dd1 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/update_case.ts @@ -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 { schema } from '@kbn/config-schema'; +import { wrapError } from './utils'; +import { RouteDeps } from '.'; +import { UpdatedCaseSchema } from './schema'; + +export function initUpdateCaseApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: UpdatedCaseSchema, + }, + }, + async (context, request, response) => { + try { + const updatedCase = await caseService.updateCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + updatedAttributes: request.body, + }); + return response.ok({ body: updatedCase }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/update_comment.ts b/x-pack/plugins/case/server/routes/api/update_comment.ts new file mode 100644 index 00000000000000..e1ee6029e8e4fc --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/update_comment.ts @@ -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 { schema } from '@kbn/config-schema'; +import { wrapError } from './utils'; +import { NewCommentSchema } from './schema'; +import { RouteDeps } from '.'; + +export function initUpdateCommentApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/comment/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: NewCommentSchema, + }, + }, + async (context, request, response) => { + try { + const updatedComment = await caseService.updateComment({ + client: context.core.savedObjects.client, + commentId: request.params.id, + updatedAttributes: request.body, + }); + return response.ok({ body: updatedComment }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts new file mode 100644 index 00000000000000..c6e33dbb8433bd --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/utils.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 { boomify, isBoom } from 'boom'; +import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; +import { + NewCaseType, + NewCaseFormatted, + NewCommentType, + NewCommentFormatted, + UserType, +} from './types'; + +export const formatNewCase = ( + newCase: NewCaseType, + { full_name, username }: { full_name?: string; username: string } +): NewCaseFormatted => ({ + created_at: new Date().valueOf(), + created_by: { full_name, username }, + ...newCase, +}); + +interface NewCommentArgs { + newComment: NewCommentType; + full_name?: UserType['full_name']; + username: UserType['username']; +} +export const formatNewComment = ({ + newComment, + full_name, + username, +}: NewCommentArgs): NewCommentFormatted => ({ + ...newComment, + created_at: new Date().valueOf(), + created_by: { full_name, username }, +}); + +export function wrapError(error: any): CustomHttpResponseOptions { + const boom = isBoom(error) ? error : boomify(error); + return { + body: boom, + headers: boom.output.headers, + statusCode: boom.output.statusCode, + }; +} diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts new file mode 100644 index 00000000000000..684d905a5c71fe --- /dev/null +++ b/x-pack/plugins/case/server/services/index.ts @@ -0,0 +1,192 @@ +/* + * 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, + Logger, + SavedObject, + SavedObjectsClientContract, + SavedObjectsFindResponse, + SavedObjectsUpdateResponse, + SavedObjectReference, +} from 'kibana/server'; +import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../constants'; +import { + NewCaseFormatted, + NewCommentFormatted, + UpdatedCaseType, + UpdatedCommentType, +} from '../routes/api/types'; +import { + AuthenticatedUser, + PluginSetupContract as SecurityPluginSetup, +} from '../../../security/server'; + +interface ClientArgs { + client: SavedObjectsClientContract; +} + +interface GetCaseArgs extends ClientArgs { + caseId: string; +} +interface GetCommentArgs extends ClientArgs { + commentId: string; +} +interface PostCaseArgs extends ClientArgs { + attributes: NewCaseFormatted; +} + +interface PostCommentArgs extends ClientArgs { + attributes: NewCommentFormatted; + references: SavedObjectReference[]; +} +interface UpdateCaseArgs extends ClientArgs { + caseId: string; + updatedAttributes: UpdatedCaseType; +} +interface UpdateCommentArgs extends ClientArgs { + commentId: string; + updatedAttributes: UpdatedCommentType; +} + +interface GetUserArgs { + request: KibanaRequest; + response: KibanaResponseFactory; +} + +interface CaseServiceDeps { + authentication: SecurityPluginSetup['authc']; +} +export interface CaseServiceSetup { + deleteCase(args: GetCaseArgs): Promise<{}>; + deleteComment(args: GetCommentArgs): Promise<{}>; + getAllCases(args: ClientArgs): Promise; + getAllCaseComments(args: GetCaseArgs): Promise; + getCase(args: GetCaseArgs): Promise; + getComment(args: GetCommentArgs): Promise; + getUser(args: GetUserArgs): Promise; + postNewCase(args: PostCaseArgs): Promise; + postNewComment(args: PostCommentArgs): Promise; + updateCase(args: UpdateCaseArgs): Promise; + updateComment(args: UpdateCommentArgs): Promise; +} + +export class CaseService { + constructor(private readonly log: Logger) {} + public setup = async ({ authentication }: CaseServiceDeps): Promise => ({ + deleteCase: async ({ client, caseId }: GetCaseArgs) => { + try { + this.log.debug(`Attempting to GET case ${caseId}`); + return await client.delete(CASE_SAVED_OBJECT, caseId); + } catch (error) { + this.log.debug(`Error on GET case ${caseId}: ${error}`); + throw error; + } + }, + deleteComment: async ({ client, commentId }: GetCommentArgs) => { + try { + this.log.debug(`Attempting to GET comment ${commentId}`); + return await client.delete(CASE_COMMENT_SAVED_OBJECT, commentId); + } catch (error) { + this.log.debug(`Error on GET comment ${commentId}: ${error}`); + throw error; + } + }, + getCase: async ({ client, caseId }: GetCaseArgs) => { + try { + this.log.debug(`Attempting to GET case ${caseId}`); + return await client.get(CASE_SAVED_OBJECT, caseId); + } catch (error) { + this.log.debug(`Error on GET case ${caseId}: ${error}`); + throw error; + } + }, + getComment: async ({ client, commentId }: GetCommentArgs) => { + try { + this.log.debug(`Attempting to GET comment ${commentId}`); + return await client.get(CASE_COMMENT_SAVED_OBJECT, commentId); + } catch (error) { + this.log.debug(`Error on GET comment ${commentId}: ${error}`); + throw error; + } + }, + getAllCases: async ({ client }: ClientArgs) => { + try { + this.log.debug(`Attempting to GET all cases`); + return await client.find({ type: CASE_SAVED_OBJECT }); + } catch (error) { + this.log.debug(`Error on GET cases: ${error}`); + throw error; + } + }, + getAllCaseComments: async ({ client, caseId }: GetCaseArgs) => { + try { + this.log.debug(`Attempting to GET all comments for case ${caseId}`); + return await client.find({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + }); + } catch (error) { + this.log.debug(`Error on GET all comments for case ${caseId}: ${error}`); + throw error; + } + }, + getUser: async ({ request, response }: GetUserArgs) => { + let user; + try { + this.log.debug(`Attempting to authenticate a user`); + user = await authentication!.getCurrentUser(request); + } catch (error) { + this.log.debug(`Error on GET user: ${error}`); + throw error; + } + if (!user) { + this.log.debug(`Error on GET user: Bad User`); + throw new Error('Bad User - the user is not authenticated'); + } + return user; + }, + postNewCase: async ({ client, attributes }: PostCaseArgs) => { + try { + this.log.debug(`Attempting to POST a new case`); + return await client.create(CASE_SAVED_OBJECT, { ...attributes }); + } catch (error) { + this.log.debug(`Error on POST a new case: ${error}`); + throw error; + } + }, + postNewComment: async ({ client, attributes, references }: PostCommentArgs) => { + try { + this.log.debug(`Attempting to POST a new comment`); + return await client.create(CASE_COMMENT_SAVED_OBJECT, attributes, { references }); + } catch (error) { + this.log.debug(`Error on POST a new comment: ${error}`); + throw error; + } + }, + updateCase: async ({ client, caseId, updatedAttributes }: UpdateCaseArgs) => { + try { + this.log.debug(`Attempting to UPDATE case ${caseId}`); + return await client.update(CASE_SAVED_OBJECT, caseId, { ...updatedAttributes }); + } catch (error) { + this.log.debug(`Error on UPDATE case ${caseId}: ${error}`); + throw error; + } + }, + updateComment: async ({ client, commentId, updatedAttributes }: UpdateCommentArgs) => { + try { + this.log.debug(`Attempting to UPDATE comment ${commentId}`); + return await client.update(CASE_COMMENT_SAVED_OBJECT, commentId, { + ...updatedAttributes, + }); + } catch (error) { + this.log.debug(`Error on UPDATE comment ${commentId}: ${error}`); + throw error; + } + }, + }); +} diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts index 0b5a3533bd3b69..9547a2dc529665 100644 --- a/x-pack/plugins/licensing/server/plugin.test.ts +++ b/x-pack/plugins/licensing/server/plugin.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BehaviorSubject } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; import moment from 'moment'; import { LicenseType } from '../common/types'; @@ -53,7 +52,7 @@ describe('licensing plugin', () => { features: {}, }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const license = await license$.pipe(take(1)).toPromise(); @@ -71,7 +70,7 @@ describe('licensing plugin', () => { }) ); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise(); @@ -85,7 +84,7 @@ describe('licensing plugin', () => { const dataClient = elasticsearchServiceMock.createClusterClient(); dataClient.callAsInternalUser.mockRejectedValue(new Error('test')); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const license = await license$.pipe(take(1)).toPromise(); @@ -99,7 +98,7 @@ describe('licensing plugin', () => { error.status = 400; dataClient.callAsInternalUser.mockRejectedValue(error); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const license = await license$.pipe(take(1)).toPromise(); @@ -119,7 +118,7 @@ describe('licensing plugin', () => { .mockResolvedValue({ license: buildRawLicense(), features: {} }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise(); @@ -137,7 +136,7 @@ describe('licensing plugin', () => { }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; await plugin.setup(coreSetup); await flushPromises(); @@ -152,7 +151,7 @@ describe('licensing plugin', () => { }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; await plugin.setup(coreSetup); await flushPromises(); @@ -180,7 +179,7 @@ describe('licensing plugin', () => { ); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise(); @@ -209,7 +208,7 @@ describe('licensing plugin', () => { features: {}, }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { refresh } = await plugin.setup(coreSetup); expect(dataClient.callAsInternalUser).toHaveBeenCalledTimes(0); @@ -242,7 +241,7 @@ describe('licensing plugin', () => { features: {}, }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { createLicensePoller, license$ } = await plugin.setup(coreSetup); const customClient = elasticsearchServiceMock.createClusterClient(); diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index 2eabd534a997c5..383245e6f4ee8a 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -92,7 +92,7 @@ export class LicensingPlugin implements Plugin { this.logger.debug('Setting up Licensing plugin'); const config = await this.config$.pipe(take(1)).toPromise(); const pollingFrequency = config.api_polling_frequency; - const dataClient = await core.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + const dataClient = await core.elasticsearch.dataClient; const { refresh, license$ } = this.createLicensePoller( dataClient, diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 33f554be5caa3b..17e49b8cf40d30 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -17,6 +17,7 @@ import { Plugin, PluginSetupContract, PluginSetupDependencies } from './plugin'; // These exports are part of public Security plugin contract, any change in signature of exported // functions or removal of exports should be considered as a breaking change. export { + Authentication, AuthenticationResult, DeauthenticationResult, CreateAPIKeyResult, @@ -24,6 +25,7 @@ export { InvalidateAPIKeyResult, } from './authentication'; export { PluginSetupContract }; +export { AuthenticatedUser } from '../common/model'; export const config: PluginConfigDescriptor> = { schema: ConfigSchema, diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 05d67b112bad89..5e32a0e90198a6 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -6,7 +6,7 @@ import { of } from 'rxjs'; import { ByteSizeValue } from '@kbn/config-schema'; -import { IClusterClient, CoreSetup } from '../../../../src/core/server'; +import { ICustomClusterClient, CoreSetup } from '../../../../src/core/server'; import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; import { Plugin, PluginSetupDependencies } from './plugin'; @@ -15,7 +15,7 @@ import { coreMock, elasticsearchServiceMock } from '../../../../src/core/server/ describe('Security Plugin', () => { let plugin: Plugin; let mockCoreSetup: MockedKeys; - let mockClusterClient: jest.Mocked; + let mockClusterClient: jest.Mocked; let mockDependencies: PluginSetupDependencies; beforeEach(() => { plugin = new Plugin( @@ -35,9 +35,9 @@ describe('Security Plugin', () => { mockCoreSetup = coreMock.createSetup(); mockCoreSetup.http.isTlsEnabled = true; - mockClusterClient = elasticsearchServiceMock.createClusterClient(); + mockClusterClient = elasticsearchServiceMock.createCustomClusterClient(); mockCoreSetup.elasticsearch.createClient.mockReturnValue( - (mockClusterClient as unknown) as jest.Mocked + (mockClusterClient as unknown) as jest.Mocked ); mockDependencies = { licensing: { license$: of({}) } } as PluginSetupDependencies; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 9c4b01f94ef4df..ce682d8b30eb76 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -7,7 +7,7 @@ import { combineLatest } from 'rxjs'; import { first } from 'rxjs/operators'; import { - IClusterClient, + ICustomClusterClient, CoreSetup, KibanaRequest, Logger, @@ -85,7 +85,7 @@ export interface PluginSetupDependencies { */ export class Plugin { private readonly logger: Logger; - private clusterClient?: IClusterClient; + private clusterClient?: ICustomClusterClient; private spacesService?: SpacesService | symbol = Symbol('not accessed'); private securityLicenseService?: SecurityLicenseService; diff --git a/x-pack/plugins/spaces/server/lib/create_default_space.ts b/x-pack/plugins/spaces/server/lib/create_default_space.ts index 2301fa26dab288..0d1a4ddab91bb5 100644 --- a/x-pack/plugins/spaces/server/lib/create_default_space.ts +++ b/x-pack/plugins/spaces/server/lib/create_default_space.ts @@ -9,7 +9,7 @@ import { SavedObjectsLegacyService, IClusterClient } from 'src/core/server'; import { DEFAULT_SPACE_ID } from '../../common/constants'; interface Deps { - esClient: Pick; + esClient: IClusterClient; savedObjects: SavedObjectsLegacyService; } diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index 2b0cfd3687a24a..c1f557f164ad65 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -168,7 +168,7 @@ describe('onPostAuthInterceptor', () => { const spacesService = await service.setup({ http: (http as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts index 9e1bb5cf9f7d6d..a3396e98c35122 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts @@ -50,7 +50,7 @@ describe('createSpacesTutorialContextFactory', () => { it('should create context with the current space id for the default space', async () => { const spacesService = await service.setup({ http: coreMock.createSetup().http, - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index dc4bc839c0e29a..6b7699100032d1 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -5,7 +5,6 @@ */ import { Observable } from 'rxjs'; -import { take } from 'rxjs/operators'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { HomeServerPluginSetup } from 'src/plugins/home/server'; import { @@ -168,9 +167,8 @@ export class Plugin { ); }, createDefaultSpace: async () => { - const esClient = await core.elasticsearch.adminClient$.pipe(take(1)).toPromise(); - return createDefaultSpace({ - esClient, + return await createDefaultSpace({ + esClient: core.elasticsearch.adminClient, savedObjects: this.getLegacyAPI().savedObjects, }); }, diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 4d8d08a487e9a9..74197e6ca7556d 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -43,7 +43,7 @@ describe('copy to space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts index 28d5708a3873ca..35f18cf66a57e0 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts @@ -44,7 +44,7 @@ describe('Spaces Public API', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts index f9bd4494791f1f..3300e308252834 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts @@ -42,7 +42,7 @@ describe('GET space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts index 02219db88a04c2..ca89731f35946f 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts @@ -42,7 +42,7 @@ describe('GET /spaces/space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index d82ccaa8ff3806..26ecbf2247e0f2 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -43,7 +43,7 @@ describe('Spaces Public API', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index 15837110f4d927..e6182e027b854d 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -44,7 +44,7 @@ describe('PUT /api/spaces/space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts b/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts index 8f97df2aa0a501..461f816ff5019a 100644 --- a/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts @@ -22,7 +22,7 @@ describe('GET /internal/spaces/_active_space', () => { const service = new SpacesService(null as any, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: null, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts index 30ad3f399916b6..68d096e046ed4f 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts @@ -74,7 +74,7 @@ const createService = async (serverBasePath: string = '') => { const spacesServiceSetup = await spacesService.setup({ http: httpSetup, - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), config$: Rx.of(spacesConfig), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => new SpacesAuditLogger({}), diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts index b8d0f910a42ea3..f8ed58fa575518 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts @@ -5,7 +5,7 @@ */ import { map, take } from 'rxjs/operators'; -import { Observable, Subscription, combineLatest } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { Legacy } from 'kibana'; import { Logger, KibanaRequest, CoreSetup } from '../../../../../src/core/server'; import { PluginSetupContract as SecurityPluginSetup } from '../../../security/server'; @@ -69,15 +69,15 @@ export class SpacesService { }; const getScopedClient = async (request: KibanaRequest) => { - return combineLatest(elasticsearch.adminClient$, config$) + return config$ .pipe( - map(([clusterClient, config]) => { + map(config => { const internalRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository( - clusterClient.callAsInternalUser, + elasticsearch.adminClient.callAsInternalUser, ['space'] ); - const callCluster = clusterClient.asScoped(request).callAsCurrentUser; + const callCluster = elasticsearch.adminClient.asScoped(request).callAsCurrentUser; const callWithRequestRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository( callCluster, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 83e4e6452b367c..7a62ac832b4f4e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -441,7 +441,9 @@ "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", "common.ui.management.breadcrumb": "管理", - "common.ui.management.nav.menu": "管理メニュー", + "management.connectDataDisplayName": "データに接続", + "management.displayName": "管理", + "management.nav.menu": "管理メニュー", "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6b1f224b2ad9bd..e5c9bbb1617b59 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -441,7 +441,9 @@ "common.ui.flotCharts.tueLabel": "周二", "common.ui.flotCharts.wedLabel": "周三", "common.ui.management.breadcrumb": "管理", - "common.ui.management.nav.menu": "管理菜单", + "management.connectDataDisplayName": "连接数据", + "management.displayName": "管理", + "management.nav.menu": "管理菜单", "common.ui.modals.cancelButtonLabel": "取消", "common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 2b92e70fb30afa..86db39823ba91c 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -4,38 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ -require('@kbn/plugin-helpers').babelRegister(); -require('@kbn/test').runTestsCli([ +const alwaysImportedTests = [require.resolve('../test/functional/config.js')]; +const onlyNotInCoverageTests = [ require.resolve('../test/reporting/configs/chromium_api.js'), require.resolve('../test/reporting/configs/chromium_functional.js'), - require.resolve('../test/reporting/configs/generate_api'), - require.resolve('../test/functional/config.js'), + require.resolve('../test/reporting/configs/generate_api.js'), require.resolve('../test/api_integration/config_security_basic.js'), require.resolve('../test/api_integration/config.js'), require.resolve('../test/alerting_api_integration/spaces_only/config.ts'), require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'), require.resolve('../test/plugin_api_integration/config.js'), - require.resolve('../test/plugin_functional/config'), - require.resolve('../test/kerberos_api_integration/config'), - require.resolve('../test/kerberos_api_integration/anonymous_access.config'), - require.resolve('../test/saml_api_integration/config'), - require.resolve('../test/token_api_integration/config'), - require.resolve('../test/oidc_api_integration/config'), - require.resolve('../test/oidc_api_integration/implicit_flow.config'), - require.resolve('../test/pki_api_integration/config'), - require.resolve('../test/spaces_api_integration/spaces_only/config'), - require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial'), - require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic'), - require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial'), - require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic'), - require.resolve('../test/saved_object_api_integration/security_only/config_trial'), - require.resolve('../test/saved_object_api_integration/security_only/config_basic'), - require.resolve('../test/saved_object_api_integration/spaces_only/config'), - require.resolve('../test/ui_capabilities/security_and_spaces/config'), - require.resolve('../test/ui_capabilities/security_only/config'), - require.resolve('../test/ui_capabilities/spaces_only/config'), - require.resolve('../test/upgrade_assistant_integration/config'), - require.resolve('../test/licensing_plugin/config'), - require.resolve('../test/licensing_plugin/config.public'), - require.resolve('../test/licensing_plugin/config.legacy'), + require.resolve('../test/plugin_functional/config.ts'), + require.resolve('../test/kerberos_api_integration/config.ts'), + require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'), + require.resolve('../test/saml_api_integration/config.ts'), + require.resolve('../test/token_api_integration/config.js'), + require.resolve('../test/oidc_api_integration/config.ts'), + require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'), + require.resolve('../test/pki_api_integration/config.ts'), + require.resolve('../test/spaces_api_integration/spaces_only/config.ts'), + require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'), + require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic.ts'), + require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial.ts'), + require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic.ts'), + require.resolve('../test/saved_object_api_integration/security_only/config_trial.ts'), + require.resolve('../test/saved_object_api_integration/security_only/config_basic.ts'), + require.resolve('../test/saved_object_api_integration/spaces_only/config.ts'), + require.resolve('../test/ui_capabilities/security_and_spaces/config.ts'), + require.resolve('../test/ui_capabilities/security_only/config.ts'), + require.resolve('../test/ui_capabilities/spaces_only/config.ts'), + require.resolve('../test/upgrade_assistant_integration/config.js'), + require.resolve('../test/licensing_plugin/config.ts'), + require.resolve('../test/licensing_plugin/config.public.ts'), + require.resolve('../test/licensing_plugin/config.legacy.ts'), +]; + +require('@kbn/plugin-helpers').babelRegister(); +require('@kbn/test').runTestsCli([ + ...alwaysImportedTests, + ...(!!process.env.CODE_COVERAGE ? [] : onlyNotInCoverageTests), ]); diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js index ef7124e2864f8c..db705b301a71ec 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js @@ -86,6 +86,9 @@ export default function({ getService }) { expect(stats.stack_stats.kibana.plugins.reporting.enabled).to.be(true); expect(stats.stack_stats.kibana.plugins.rollups.index_patterns).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.spaces.available).to.be(true); + expect(stats.stack_stats.kibana.plugins.fileUploadTelemetry.filesUploadedTotalCount).to.be.a( + 'number' + ); expect(stats.stack_stats.kibana.os.platforms[0].platform).to.be.a('string'); expect(stats.stack_stats.kibana.os.platforms[0].count).to.be(1); diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index d1ae7138ecc65f..639b65ec5eca87 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -22,7 +22,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { state: undefined, }; const expectedSearchString = - "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default"; + "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194),streamLive:!f)&sourceId=default"; const expectedRedirectPath = '/logs/stream?'; await pageObjects.common.navigateToActualUrl( diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index 630ec2792b9bf4..1f5a64835416aa 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -100,7 +100,7 @@ export default function({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/43938 - it.skip('should succeed if both the OpenID Connect response and the cookie are provided', async () => { + it('should succeed if both the OpenID Connect response and the cookie are provided', async () => { const { idToken, accessToken } = createTokens('1', stateAndNonce.nonce); const authenticationResponse = `https://kibana.com/api/security/oidc/implicit#id_token=${idToken}&state=${stateAndNonce.state}&token_type=bearer&access_token=${accessToken}`; diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 7383bb3409f1a6..00ab75702e90d1 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -87,14 +87,8 @@ export function copyToSpaceTestSuiteFactory( body: { size: 0, query: { - bool: { - must_not: { - term: { - // exclude spaces from the result set. - // we don't assert on these. - type: 'space', - }, - }, + terms: { + type: ['visualization', 'dashboard', 'index-pattern'], }, }, aggs: { diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index e3994634be1d9b..9036fcbf7a8ddc 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -39,6 +39,11 @@ export function deleteTestSuiteFactory(es: any, esArchiver: any, supertest: Supe index: '.kibana', body: { size: 0, + query: { + terms: { + type: ['visualization', 'dashboard', 'space', 'config', 'index-pattern'], + }, + }, aggs: { count: { terms: { diff --git a/yarn.lock b/yarn.lock index 0026370927fe10..96ec5213badcb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3462,10 +3462,10 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/eslint@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-6.1.2.tgz#297ece0f3815f93d699b18bdade5e6bee747284f" - integrity sha512-t+smTKg1e9SshiIOI94Zi+Lvo3bfHF20MuKP8w3VGWdrS1dYm33A7xrSoyy9FQv6oE2TwYqEXVJ50I0or8+FWQ== +"@types/eslint@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-6.1.3.tgz#ec2a66e445a48efaa234020eb3b6e8f06afc9c61" + integrity sha512-llYf1QNZaDweXtA7uY6JczcwHmFwJL9TpK3E6sY0B18l6ulDT6VWNMAdEjYccFHiDfxLPxffd8QmSDV4QUUspA== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -4003,10 +4003,10 @@ dependencies: "@types/react" "*" -"@types/react-beautiful-dnd@^11.0.3": - version "11.0.3" - resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-11.0.3.tgz#51d9f37942dd18cc4aa10da98a5c883664e7ee46" - integrity sha512-7ZbT/7mNJu+uRrUGdTQ1hAINtqg909L4NHrXyspV42fvVgBgda6ysiBzoDUMENmQ/RlRJdpyrcp8Dtd/77bp9Q== +"@types/react-beautiful-dnd@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-11.0.4.tgz#25cdf16864df8fd1d82f9416c8c0fd957e793024" + integrity sha512-a1Nvt1AcSEA962OuXrk1gu5bJQhzu0B3qFNO999/0nmF+oAD7HIAY0DwraS3L3XM1cVuRO1+PtpTkD4CfRK2QA== dependencies: "@types/react" "*" @@ -4148,10 +4148,10 @@ dependencies: redux "^4.0.0" -"@types/redux-actions@^2.2.1": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.3.0.tgz#d28d7913ec86ee9e20ecb33a1fed887ecb538149" - integrity sha512-N5gZT7Tg5HGRbQH56D6umLhv1R4koEFjfz5+2TFo/tjAz3Y3Aj+hjQBum3UUO4D53hYO439UlWP5Q+S63vujrQ== +"@types/redux-actions@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.1.tgz#0940e97fa35ad3004316bddb391d8e01d2efa605" + integrity sha512-zKgK+ATp3sswXs6sOYo1tk8xdXTy4CTaeeYrVQlClCjeOpag5vzPo0ASWiiBJ7vsiQRAdb3VkuFLnDoBimF67g== "@types/redux@^3.6.31": version "3.6.31" @@ -4434,57 +4434,40 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.12.0.tgz#0da7cbca7b24f4c6919e9eb31c704bfb126f90ad" - integrity sha512-1t4r9rpLuEwl3hgt90jY18wJHSyb0E3orVL3DaqwmpiSDHmHiSspVsvsFF78BJ/3NNG3qmeso836jpuBWYziAA== +"@typescript-eslint/eslint-plugin@^2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.15.0.tgz#5442c30b687ffd576ff74cfea46a6d7bfb0ee893" + integrity sha512-XRJFznI5v4K1WvIrWmjFjBAdQWaUTz4xJEdqR7+wAFsv6Q9dP3mOlE6BMNT3pdlp9eF1+bC5m5LZTmLMqffCVw== dependencies: - "@typescript-eslint/experimental-utils" "2.12.0" + "@typescript-eslint/experimental-utils" "2.15.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.12.0.tgz#e0a76ffb6293e058748408a191921e453c31d40d" - integrity sha512-jv4gYpw5N5BrWF3ntROvCuLe1IjRenLy5+U57J24NbPGwZFAjhnM45qpq0nDH1y/AZMb3Br25YiNVwyPbz6RkA== +"@typescript-eslint/experimental-utils@2.15.0", "@typescript-eslint/experimental-utils@^2.5.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.15.0.tgz#41e35313bfaef91650ddb5380846d1c78a780070" + integrity sha512-Qkxu5zndY5hqlcQkmA88gfLvqQulMpX/TN91XC7OuXsRf4XG5xLGie0sbpX97o/oeccjeZYRMipIsjKk/tjDHA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.12.0" + "@typescript-eslint/typescript-estree" "2.15.0" eslint-scope "^5.0.0" -"@typescript-eslint/experimental-utils@^1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" - integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-scope "^4.0.0" - -"@typescript-eslint/parser@^2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.12.0.tgz#393f1604943a4ca570bb1a45bc8834e9b9158884" - integrity sha512-lPdkwpdzxEfjI8TyTzZqPatkrswLSVu4bqUgnB03fHSOwpC7KSerPgJRgIAf11UGNf7HKjJV6oaPZI4AghLU6g== +"@typescript-eslint/parser@^2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.15.0.tgz#379a71a51b0429bc3bc55c5f8aab831bf607e411" + integrity sha512-6iSgQsqAYTaHw59t0tdjzZJluRAjswdGltzKEdLtcJOxR2UVTPHYvZRqkAVGCkaMVb6Fpa60NnuozNCvsSpA9g== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.12.0" - "@typescript-eslint/typescript-estree" "2.12.0" + "@typescript-eslint/experimental-utils" "2.15.0" + "@typescript-eslint/typescript-estree" "2.15.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" - integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== - dependencies: - lodash.unescape "4.0.1" - semver "5.5.0" - -"@typescript-eslint/typescript-estree@2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.12.0.tgz#bd9e547ccffd17dfab0c3ab0947c80c8e2eb914c" - integrity sha512-rGehVfjHEn8Frh9UW02ZZIfJs6SIIxIu/K1bbci8rFfDE/1lQ8krIJy5OXOV3DVnNdDPtoiPOdEANkLMrwXbiQ== +"@typescript-eslint/typescript-estree@2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.15.0.tgz#79ae52eed8701b164d91e968a65d85a9105e76d3" + integrity sha512-L6Pog+w3VZzXkAdyqA0VlwybF8WcwZX+mufso86CMxSdWmcizJ38lgBdpqTbc9bo92iyi0rOvmATKiwl+amjxg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -4799,7 +4782,7 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" -acorn-jsx@^5.0.2: +acorn-jsx@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== @@ -11432,10 +11415,10 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-config-prettier@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz#0a04f147e31d33c6c161b2dd0971418ac52d0477" - integrity sha512-YrKucoFdc7SEko5Sxe4r6ixqXPDP1tunGw91POeZTTRKItf/AMFYt/YLEQtZMkR2LVpAVhcAcZgcWpm1oGPW7w== +eslint-config-prettier@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.9.0.tgz#430d24822e82f7deb1e22a435bfa3999fae4ad64" + integrity sha512-k4E14HBtcLv0uqThaI6I/n1LEqROp8XaPu6SO9Z32u5NlGRC07Enu1Bh2KEFw4FNHbekH8yzbIU9kUGxbiGmCA== dependencies: get-stdin "^6.0.0" @@ -11474,20 +11457,12 @@ eslint-import-resolver-webpack@0.11.1: resolve "^1.10.0" semver "^5.3.0" -eslint-module-utils@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c" - integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw== - dependencies: - debug "^2.6.8" - pkg-dir "^2.0.0" - -eslint-module-utils@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a" - integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw== +eslint-module-utils@2.5.0, eslint-module-utils@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz#cdf0b40d623032274ccd2abd7e64c4e524d6e19c" + integrity sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw== dependencies: - debug "^2.6.8" + debug "^2.6.9" pkg-dir "^2.0.0" eslint-plugin-babel@^5.3.0: @@ -11497,51 +11472,57 @@ eslint-plugin-babel@^5.3.0: dependencies: eslint-rule-composer "^0.3.0" -eslint-plugin-ban@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-ban/-/eslint-plugin-ban-1.3.0.tgz#be9714cb9e01a1adec6c86cdb182e97636eafe44" - integrity sha512-A9A2z60UeVj7/BdKzeIjVEGAog/4QXAyOkZ98AUnZc7fsRp+J7YW7+U/YEVpBJqjSiU/FGUA5tGJoI34ul/TyA== +eslint-plugin-ban@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-ban/-/eslint-plugin-ban-1.4.0.tgz#b3a7b000412921336b1feeece5b8ce9a69dea605" + integrity sha512-wtrUOLg8WUiGDkVnmyMseLRtXYBM+bJTe2STvhqznHVj6RPAiNEVLbvDj2b0WWwY/2ldKqeaw3iHUHwfCJ8c8Q== dependencies: requireindex "~1.2.0" -eslint-plugin-cypress@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.7.0.tgz#117f14ce63698e4c4f3afea3d7e27025c8d504f0" - integrity sha512-52Lq5ePCD/8jc536e1RqtLfj33BAy1s7BlYgCjbG39J5kqUitcTlRY5i3NRoeAyPHueDwETsq0eASF44ugLosQ== +eslint-plugin-cypress@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.8.1.tgz#981a0f3658b40de430bcf05cabc96b396487c91f" + integrity sha512-jDpcP+MmjmqQO/x3bwIXgp4cl7Q66RYS5/IsuOQP4Qo2sEqE3DI8tTxBQ1EhnV5qEDd2Z2TYHR+5vYI6oCN4uw== dependencies: globals "^11.12.0" -eslint-plugin-es@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" - integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== +eslint-plugin-es@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz#98cb1bc8ab0aa807977855e11ad9d1c9422d014b" + integrity sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng== dependencies: - eslint-utils "^1.4.2" + eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@^2.18.2: - version "2.18.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" - integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ== +eslint-plugin-eslint-plugin@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.1.0.tgz#a7a00f15a886957d855feacaafee264f039e62d5" + integrity sha512-kT3A/ZJftt28gbl/Cv04qezb/NQ1dwYIbi8lyf806XMxkus7DvOVCLIfTXMrorp322Pnoez7+zabXH29tADIDg== + +eslint-plugin-import@^2.19.1: + version "2.19.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.19.1.tgz#5654e10b7839d064dd0d46cd1b88ec2133a11448" + integrity sha512-x68131aKoCZlCae7rDXKSAQmbT5DQuManyXo2sK6fJJ0aK5CWAkv6A6HJZGgqC8IhjQxYPgo6/IY4Oz8AFsbBw== dependencies: array-includes "^3.0.3" + array.prototype.flat "^1.2.1" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.0" + eslint-module-utils "^2.4.1" has "^1.0.3" minimatch "^3.0.4" object.values "^1.1.0" read-pkg-up "^2.0.0" - resolve "^1.11.0" + resolve "^1.12.0" -eslint-plugin-jest@^22.19.0: - version "22.19.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b" - integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg== +eslint-plugin-jest@^23.3.0: + version "23.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.3.0.tgz#b1443d0c46d6a0de9ef3de78176dd6688c7d5326" + integrity sha512-GE6CR4ESJeu6Huw7vfZfaXHmX2R2kCFvf2X9OMcOxfP158yLKgLWz7PqLYTwRDACi84IhpmRxO8lK7GGwG05UQ== dependencies: - "@typescript-eslint/experimental-utils" "^1.13.0" + "@typescript-eslint/experimental-utils" "^2.5.0" eslint-plugin-jsx-a11y@^6.2.3: version "6.2.3" @@ -11558,10 +11539,10 @@ eslint-plugin-jsx-a11y@^6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" -eslint-plugin-mocha@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.0.tgz#16ff9ce4d5a6a35af522d5db0ce3c8946566e4c1" - integrity sha512-vE/+tHJVom2BkMOiwkOKcAM5YqGPk3C6gMvQ32DHihKkaXF6vmxtj3UEOg64wP3m8/Zk5V/UmQbFE5nqu1EXSg== +eslint-plugin-mocha@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz#6ef4b78bd12d744beb08a06e8209de330985100d" + integrity sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA== dependencies: ramda "^0.26.1" @@ -11570,13 +11551,13 @@ eslint-plugin-no-unsanitized@^3.0.2: resolved "https://registry.yarnpkg.com/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-3.0.2.tgz#83c6fcf8e34715112757e03dd4ee436dce29ed45" integrity sha512-JnwpoH8Sv4QOjrTDutENBHzSnyYtspdjtglYtqUtAHe6f6LLKqykJle+UwFPg23GGwt5hI3amS9CRDezW8GAww== -eslint-plugin-node@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" - integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== +eslint-plugin-node@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz#365944bb0804c5d1d501182a9bc41a0ffefed726" + integrity sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg== dependencies: - eslint-plugin-es "^2.0.0" - eslint-utils "^1.4.2" + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" ignore "^5.1.1" minimatch "^3.0.4" resolve "^1.10.1" @@ -11587,32 +11568,33 @@ eslint-plugin-prefer-object-spread@^1.2.1: resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-object-spread/-/eslint-plugin-prefer-object-spread-1.2.1.tgz#27fb91853690cceb3ae6101d9c8aecc6a67a402c" integrity sha1-J/uRhTaQzOs65hAdnIrsxqZ6QCw= -eslint-plugin-prettier@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba" - integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA== +eslint-plugin-prettier@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba" + integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA== dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.1.2.tgz#1358d2acb2c5e02b7e90c37e611ac258a488e3a7" - integrity sha512-ZR+AyesAUGxJAyTFlF3MbzeVHAcQTFQt1fFVe5o0dzY/HFoj1dgQDMoIkiM+ltN/HhlHBYX4JpJwYonjxsyQMA== +eslint-plugin-react-hooks@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz#53e073961f1f5ccf8dd19558036c1fac8c29d99a" + integrity sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw== -eslint-plugin-react@^7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09" - integrity sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug== +eslint-plugin-react@^7.17.0: + version "7.17.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.17.0.tgz#a31b3e134b76046abe3cd278e7482bd35a1d12d7" + integrity sha512-ODB7yg6lxhBVMeiH1c7E95FLD4E/TwmFjltiU+ethv7KPdCwgiFuOZg9zNRHyufStTDLl/dEFqI2Q1VPmCd78A== dependencies: array-includes "^3.0.3" doctrine "^2.1.0" + eslint-plugin-eslint-plugin "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.2.1" + jsx-ast-utils "^2.2.3" object.entries "^1.1.0" - object.fromentries "^2.0.0" + object.fromentries "^2.0.1" object.values "^1.1.0" prop-types "^15.7.2" - resolve "^1.12.0" + resolve "^1.13.1" eslint-rule-composer@^0.3.0: version "0.3.0" @@ -11643,13 +11625,20 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2, eslint-utils@^1.4.3: +eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" @@ -11699,10 +11688,10 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.1.tgz#828e4c469697d43bb586144be152198b91e96ed6" - integrity sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A== +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -11711,19 +11700,19 @@ eslint@^6.5.1: debug "^4.0.1" doctrine "^3.0.0" eslint-scope "^5.0.0" - eslint-utils "^1.4.2" + eslint-utils "^1.4.3" eslint-visitor-keys "^1.1.0" - espree "^6.1.1" + espree "^6.1.2" esquery "^1.0.1" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" - globals "^11.7.0" + globals "^12.1.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.4.1" + inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" @@ -11732,7 +11721,7 @@ eslint@^6.5.1: minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.2" + optionator "^0.8.3" progress "^2.0.0" regexpp "^2.0.1" semver "^6.1.2" @@ -11750,13 +11739,13 @@ espree@^3.1.6: acorn "^5.5.0" acorn-jsx "^3.0.0" -espree@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de" - integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ== +espree@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" + integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== dependencies: - acorn "^7.0.0" - acorn-jsx "^5.0.2" + acorn "^7.1.0" + acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" esprima@2.7.x, esprima@^2.7.1: @@ -12380,11 +12369,16 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-memoize@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.1.tgz#c3519241e80552ce395e1a32dcdde8d1fd680f5d" + integrity sha512-xdmw296PCL01tMOXx9mdJSmWY29jQgxyuZdq0rEHMu+Tpe1eOEtCycoG6chzlcrWsNgpZP7oL8RiQr7+G6Bl6g== + fast-safe-stringify@^2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2" @@ -13832,7 +13826,7 @@ global@^4.4.0: min-document "^2.19.0" process "^0.11.10" -globals@^11.1.0, globals@^11.7.0: +globals@^11.1.0: version "11.7.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673" integrity sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg== @@ -13842,6 +13836,13 @@ globals@^11.12.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^12.1.0: + version "12.3.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13" + integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw== + dependencies: + type-fest "^0.8.1" + globals@^9.18.0, globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -15746,25 +15747,6 @@ inquirer@^6.2.0: strip-ansi "^4.0.0" through "^2.3.6" -inquirer@^6.4.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - inquirer@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a" @@ -17610,6 +17592,14 @@ jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" +jsx-ast-utils@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" + integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== + dependencies: + array-includes "^3.0.3" + object.assign "^4.1.0" + jsx-to-string@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/jsx-to-string/-/jsx-to-string-1.4.0.tgz#66dc34d773dab9f40fe993cff9940e5da655b705" @@ -17629,6 +17619,11 @@ jszip@^3.1.5: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" +just-curry-it@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5" + integrity sha512-mjzgSOFzlrurlURaHVjnQodyPNvrHrf1TbQP2XU9NSqBtHQPuHZ+Eb6TAJP7ASeJN9h9K0KXoRTs8u6ouHBKvg== + just-debounce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" @@ -18346,7 +18341,7 @@ locutus@^2.0.5: resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917" integrity sha512-AZg2kCqrquMJ5FehDsBidV0qHl98NrsYtseUClzjAQ3HFnsDBJTCwGVplSQ82t9/QfgahqvTjaKcZqZkHmS0wQ== -lodash-es@^4.17.11, lodash-es@^4.17.4, lodash-es@^4.17.5, lodash-es@^4.2.1: +lodash-es@^4.17.11, lodash-es@^4.17.5, lodash-es@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== @@ -20806,7 +20801,7 @@ object.fromentries@^1.0.0: function-bind "^1.1.1" has "^1.0.1" -object.fromentries@^2.0.0, object.fromentries@^2.0.1: +object.fromentries@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.1.tgz#050f077855c7af8ae6649f45c80b16ee2d31e704" integrity sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA== @@ -20992,7 +20987,7 @@ optional-js@^2.0.0: resolved "https://registry.yarnpkg.com/optional-js/-/optional-js-2.1.1.tgz#c2dc519ad119648510b4d241dbb60b1167c36a46" integrity sha512-mUS4bDngcD5kKzzRUd1HVQkr9Lzzby3fSrrPR9wOHhQiyYo+hDS5NVli5YQzGjQRQ15k5Sno4xH9pfykJdeEUA== -optionator@^0.8.1, optionator@^0.8.2: +optionator@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= @@ -21004,6 +20999,18 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + ora@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" @@ -22955,6 +22962,13 @@ re-reselect@^3.4.0: resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd" integrity sha512-JsecfN+JlckncVXTWFWjn0Vk6uInl8GSf4eEd9tTk5qXHlgqkPdILpnYpgZcISXNYAzvfvsCZviaDk8AxyS5sg== +re-resizable@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.1.1.tgz#7ff7cfe92c0b9d8b0bceaa578aadaeeff8931eaf" + integrity sha512-ngzX5xbXi9LlIghJUYZaBDkJUIMLYqO3tQ2cJZoNprCRGhfHnbyufKm51MZRIOBlLigLzPPFKBxQE8ZLezKGfA== + dependencies: + fast-memoize "^2.5.1" + react-ace@^5.5.0: version "5.10.0" resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e" @@ -24085,25 +24099,21 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" -reduce-reducers@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.2.tgz#fa1b4718bc5292a71ddd1e5d839c9bea9770f14b" - integrity sha1-+htHGLxSkqcd3R5dg5yb6pdw8Us= - reduce-reducers@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c" integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw== -redux-actions@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.2.1.tgz#d64186b25649a13c05478547d7cd7537b892410d" - integrity sha1-1kGGslZJoTwFR4VH1811N7iSQQ0= +redux-actions@2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e" + integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw== dependencies: - invariant "^2.2.1" - lodash "^4.13.1" - lodash-es "^4.17.4" - reduce-reducers "^0.1.0" + invariant "^2.2.4" + just-curry-it "^3.1.0" + loose-envify "^1.4.0" + reduce-reducers "^0.4.3" + to-camel-case "^1.0.0" redux-observable@^1.0.0: version "1.0.0" @@ -24838,6 +24848,13 @@ resolve@^1.12.0, resolve@^1.4.0: dependencies: path-parse "^1.0.6" +resolve@^1.13.1: + version "1.14.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" + integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ== + dependencies: + path-parse "^1.0.6" + resolve@^1.5.0, resolve@^1.7.1: version "1.7.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" @@ -27681,6 +27698,13 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-camel-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46" + integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY= + dependencies: + to-space-case "^1.0.0" + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -27691,6 +27715,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-no-case@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" + integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -27739,6 +27768,13 @@ to-source-code@^1.0.0: dependencies: is-nil "^1.0.0" +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc= + dependencies: + to-no-case "^1.0.0" + to-through@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" @@ -30371,6 +30407,11 @@ with@^5.0.0: acorn "^3.1.0" acorn-globals "^3.0.0" +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"