diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 3a3bc93a45ae3f..d627ce8cdc3d09 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -38,13 +38,11 @@ disabled: - x-pack/test/security_solution_cypress/cases_cli_config.ts - x-pack/test/security_solution_cypress/ccs_config.ts - x-pack/test/security_solution_cypress/cli_config.ts - - x-pack/test/security_solution_cypress/cli_config_parallel.ts - x-pack/test/security_solution_cypress/cli_config_investigations_parallel.ts - x-pack/test/security_solution_cypress/config.firefox.ts - x-pack/test/security_solution_cypress/config.ts - x-pack/test/security_solution_cypress/response_ops_cli_config.ts - x-pack/test/security_solution_cypress/upgrade_config.ts - - x-pack/test/security_solution_cypress/visual_config.ts - x-pack/test/threat_intelligence_cypress/visual_config.ts - x-pack/test/threat_intelligence_cypress/cli_config_parallel.ts - x-pack/test/threat_intelligence_cypress/config.ts diff --git a/.buildkite/pipelines/pull_request/defend_workflows.yml b/.buildkite/pipelines/pull_request/defend_workflows.yml index c88679a547ede8..06a4c386b472a1 100644 --- a/.buildkite/pipelines/pull_request/defend_workflows.yml +++ b/.buildkite/pipelines/pull_request/defend_workflows.yml @@ -5,6 +5,7 @@ steps: queue: n2-4-spot depends_on: build timeout_in_minutes: 120 + parallelism: 2 retry: automatic: - exit_status: '-1' diff --git a/.buildkite/pipelines/pull_request/response_ops.yml b/.buildkite/pipelines/pull_request/response_ops.yml index ab21270bce9560..2f71568f38cb5d 100644 --- a/.buildkite/pipelines/pull_request/response_ops.yml +++ b/.buildkite/pipelines/pull_request/response_ops.yml @@ -5,6 +5,7 @@ steps: queue: n2-4-spot depends_on: build timeout_in_minutes: 120 + parallelism: 4 retry: automatic: - exit_status: '-1' diff --git a/.buildkite/pipelines/pull_request/security_solution.yml b/.buildkite/pipelines/pull_request/security_solution.yml index 42e782ee86f50d..ee0bc6b6b69130 100644 --- a/.buildkite/pipelines/pull_request/security_solution.yml +++ b/.buildkite/pipelines/pull_request/security_solution.yml @@ -5,7 +5,7 @@ steps: queue: n2-4-spot depends_on: build timeout_in_minutes: 120 - parallelism: 4 + parallelism: 15 retry: automatic: - exit_status: '-1' diff --git a/.buildkite/scripts/steps/functional/defend_workflows.sh b/.buildkite/scripts/steps/functional/defend_workflows.sh index 814a6d0192b29d..d2a06a1932b8a9 100755 --- a/.buildkite/scripts/steps/functional/defend_workflows.sh +++ b/.buildkite/scripts/steps/functional/defend_workflows.sh @@ -2,16 +2,15 @@ set -euo pipefail -source .buildkite/scripts/common/util.sh - -.buildkite/scripts/bootstrap.sh -node scripts/build_kibana_platform_plugins.js +source .buildkite/scripts/steps/functional/common.sh export JOB=kibana-defend-workflows-cypress +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} -echo "--- Defend Workflows Cypress tests" +Xvfb -screen 0 1680x946x24 :99 & -node scripts/functional_tests \ - --debug --bail \ - --config x-pack/test/defend_workflows_cypress/cli_config.ts +export DISPLAY=:99 + +echo "--- Defend Workflows Cypress tests" +yarn --cwd x-pack/plugins/security_solution cypress:dw:run diff --git a/.buildkite/scripts/steps/functional/response_ops.sh b/.buildkite/scripts/steps/functional/response_ops.sh index 5604cb9774472f..066a1b599fd936 100755 --- a/.buildkite/scripts/steps/functional/response_ops.sh +++ b/.buildkite/scripts/steps/functional/response_ops.sh @@ -5,10 +5,12 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh export JOB=kibana-security-solution-chrome +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} + +Xvfb -screen 0 1680x946x24 :99 & + +export DISPLAY=:99 echo "--- Response Ops Cypress Tests on Security Solution" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config x-pack/test/security_solution_cypress/response_ops_cli_config.ts +yarn --cwd x-pack/plugins/security_solution cypress:run:respops diff --git a/.buildkite/scripts/steps/functional/response_ops_cases.sh b/.buildkite/scripts/steps/functional/response_ops_cases.sh index ee6bb2128539e9..d838307af63c72 100755 --- a/.buildkite/scripts/steps/functional/response_ops_cases.sh +++ b/.buildkite/scripts/steps/functional/response_ops_cases.sh @@ -5,10 +5,12 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh export JOB=kibana-security-solution-chrome +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} + +Xvfb -screen 0 1680x946x24 :99 & + +export DISPLAY=:99 echo "--- Response Ops Cases Cypress Tests on Security Solution" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config x-pack/test/security_solution_cypress/cases_cli_config.ts +yarn --cwd x-pack/plugins/security_solution cypress:run:cases diff --git a/.buildkite/scripts/steps/functional/security_solution.sh b/.buildkite/scripts/steps/functional/security_solution.sh index 99f605ecd6cc58..078c9af1363c60 100755 --- a/.buildkite/scripts/steps/functional/security_solution.sh +++ b/.buildkite/scripts/steps/functional/security_solution.sh @@ -5,12 +5,12 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh export JOB=kibana-security-solution-chrome -export CLI_NUMBER=${CLI_NUMBER:-$((BUILDKITE_PARALLEL_JOB+1))} -export CLI_COUNT=${CLI_COUNT:-$BUILDKITE_PARALLEL_JOB_COUNT} +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} + +Xvfb :99 -screen 0 1600x1200x24 & + +export DISPLAY=:99 echo "--- Security Solution tests (Chrome)" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config x-pack/test/security_solution_cypress/cli_config_parallel.ts +yarn --cwd x-pack/plugins/security_solution cypress:run diff --git a/.buildkite/scripts/steps/functional/security_solution_investigations.sh b/.buildkite/scripts/steps/functional/security_solution_investigations.sh index 92caea0541cb47..9c6138d3354052 100644 --- a/.buildkite/scripts/steps/functional/security_solution_investigations.sh +++ b/.buildkite/scripts/steps/functional/security_solution_investigations.sh @@ -5,12 +5,13 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh export JOB=kibana-security-solution-chrome -export CLI_NUMBER=${CLI_NUMBER:-$((BUILDKITE_PARALLEL_JOB+1))} -export CLI_COUNT=${CLI_COUNT:-$BUILDKITE_PARALLEL_JOB_COUNT} +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} -echo "--- Security Solution tests (Chrome)" -node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --config x-pack/test/security_solution_cypress/cli_config_investigations_parallel.ts +Xvfb :99 -screen 0 1600x1200x24 & + +export DISPLAY=:99 + +echo "--- Investigations Cypress Tests on Security Solution" + +yarn --cwd x-pack/plugins/security_solution cypress:investigations:run diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 7e7c4d5b9ff5cb..ac894d34e2d12a 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -478,8 +478,6 @@ Uptime:: [[release-notes-8.7.1]] == {kib} 8.7.1 -coming::[8.7.1] - Review the following information about the {kib} 8.7.1 release. [float] @@ -1089,6 +1087,18 @@ For each {kibana-ref}/xpack-spaces.html[space], complete the following to change . Enter `100`, then click *Save changes*. ==== +[discrete] +[[breaking-158338]] +.CSV reports use PIT instead of Scroll +[%collapsible] +==== +*Details* + +CSV reports now use PIT instead of Scroll. Previously generated CSV reports that used an index alias with alias-only privileges, but without privileges on the alias referenced-indices will no longer generate. For more information, refer to {kibana-pull}158338[#158338]. + +*Impact* + +To generate CSV reports, grant `read` privileges to the underlying indices. +==== + To review the breaking changes in previous versions, refer to the following: {kibana-ref-all}/8.5/release-notes-8.5.0.html#breaking-changes-8.5.0[8.5.0] | {kibana-ref-all}/8.4/release-notes-8.4.0.html#breaking-changes-8.4.0[8.4.0] | {kibana-ref-all}/8.3/release-notes-8.3.0.html#breaking-changes-8.3.0[8.3.0] | {kibana-ref-all}/8.2/release-notes-8.2.0.html#breaking-changes-8.2.0[8.2.0] | {kibana-ref-all}/8.1/release-notes-8.1.0.html#breaking-changes-8.1.0[8.1.0] | {kibana-ref-all}/8.0/release-notes-8.0.0.html#breaking-changes-8.0.0[8.0.0] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc2.html#breaking-changes-8.0.0-rc2[8.0.0-rc2] | {kibana-ref-all}/8.0/release-notes-8.0.0-rc1.html#breaking-changes-8.0.0-rc1[8.0.0-rc1] | {kibana-ref-all}/8.0/release-notes-8.0.0-beta1.html#breaking-changes-8.0.0-beta1[8.0.0-beta1] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha2.html#breaking-changes-8.0.0-alpha2[8.0.0-alpha2] | {kibana-ref-all}/8.0/release-notes-8.0.0-alpha1.html#breaking-changes-8.0.0-alpha1[8.0.0-alpha1] diff --git a/package.json b/package.json index 9020fbb393a529..551d18f1d2f98d 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@elastic/apm-rum-react": "^1.4.2", "@elastic/charts": "55.0.0", "@elastic/datemath": "5.0.3", - "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.6.0-canary.3", + "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.8.0-canary.2", "@elastic/ems-client": "8.4.0", "@elastic/eui": "80.0.0", "@elastic/filesaver": "1.1.2", @@ -1000,6 +1000,7 @@ "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", + "@cypress/grep": "^3.1.5", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.12.2", "@elastic/eslint-plugin-eui": "0.0.2", @@ -1349,6 +1350,7 @@ "chance": "1.0.18", "chromedriver": "^113.0.0", "clean-webpack-plugin": "^3.0.0", + "cli-table3": "^0.6.1", "compression-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^6.0.2", "cpy": "^8.1.1", @@ -1360,10 +1362,9 @@ "cypress-axe": "^1.4.0", "cypress-file-upload": "^5.0.8", "cypress-multi-reporters": "^1.6.3", - "cypress-pipe": "^2.0.0", "cypress-react-selector": "^3.0.0", - "cypress-real-events": "^1.7.6", - "cypress-recurse": "^1.31.2", + "cypress-real-events": "^1.8.1", + "cypress-recurse": "^1.35.1", "date-fns": "^2.29.3", "debug": "^2.6.9", "delete-empty": "^2.0.0", @@ -1439,8 +1440,8 @@ "minimist": "^1.2.6", "mocha": "^10.1.0", "mocha-junit-reporter": "^2.0.2", - "mochawesome": "^7.0.1", - "mochawesome-merge": "^4.2.1", + "mochawesome": "^7.1.3", + "mochawesome-merge": "^4.3.0", "mock-fs": "^5.1.2", "ms-chromium-edge-driver": "^0.5.1", "multistream": "^4.1.0", diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts index 8af470de187f01..c4020318974459 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts @@ -9,6 +9,7 @@ import { valid } from 'semver'; import { schema, TypeOf } from '@kbn/config-schema'; import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; +import buffer from 'buffer'; const migrationSchema = schema.object({ algorithm: schema.oneOf([schema.literal('v2'), schema.literal('zdt')], { @@ -16,6 +17,10 @@ const migrationSchema = schema.object({ }), batchSize: schema.number({ defaultValue: 1_000 }), maxBatchSizeBytes: schema.byteSize({ defaultValue: '100mb' }), // 100mb is the default http.max_content_length Elasticsearch config value + maxReadBatchSizeBytes: schema.byteSize({ + defaultValue: buffer.constants.MAX_STRING_LENGTH, + max: buffer.constants.MAX_STRING_LENGTH, + }), discardUnknownObjects: schema.maybe( schema.string({ validate: (value: string) => diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts index 3845ab745cfdba..2a0da30136a7bb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts @@ -47,6 +47,7 @@ export type { ReindexResponse, UpdateByQueryResponse, UpdateAndPickupMappingsResponse, + EsResponseTooLargeError, } from './src/actions'; export { isClusterShardLimitExceeded, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap index 421e081b44a69e..651182fb621fa5 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/__snapshots__/migrations_state_action_machine.test.ts.snap @@ -166,7 +166,9 @@ Object { "message": "Log from LEGACY_REINDEX control state", }, ], + "maxBatchSize": 1000, "maxBatchSizeBytes": 100000000, + "maxReadBatchSizeBytes": 536870888, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", @@ -392,7 +394,9 @@ Object { "message": "Log from LEGACY_DELETE control state", }, ], + "maxBatchSize": 1000, "maxBatchSizeBytes": 100000000, + "maxReadBatchSizeBytes": 536870888, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", @@ -622,7 +626,9 @@ Object { "message": "Log from LEGACY_DELETE control state", }, ], + "maxBatchSize": 1000, "maxBatchSizeBytes": 100000000, + "maxReadBatchSizeBytes": 536870888, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", @@ -856,7 +862,9 @@ Object { "message": "Log from DONE control state", }, ], + "maxBatchSize": 1000, "maxBatchSizeBytes": 100000000, + "maxReadBatchSizeBytes": 536870888, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", @@ -1110,7 +1118,9 @@ Object { "message": "Log from LEGACY_DELETE control state", }, ], + "maxBatchSize": 1000, "maxBatchSizeBytes": 100000000, + "maxReadBatchSizeBytes": 536870888, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", @@ -1347,7 +1357,9 @@ Object { "message": "Log from FATAL control state", }, ], + "maxBatchSize": 1000, "maxBatchSizeBytes": 100000000, + "maxReadBatchSizeBytes": 536870888, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts index 989e50af73683a..ffe0189abe237f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.test.ts @@ -72,7 +72,7 @@ describe('catchRetryableEsClientErrors', () => { }); }); it('ResponseError with retryable status code', async () => { - const statusCodes = [503, 401, 403, 408, 410]; + const statusCodes = [503, 401, 403, 408, 410, 429]; return Promise.all( statusCodes.map(async (status) => { const error = new esErrors.ResponseError( diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts index 168e3170d30bfe..74877c93864225 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/catch_retryable_es_client_errors.ts @@ -15,6 +15,7 @@ const retryResponseStatuses = [ 403, // AuthenticationException 408, // RequestTimeout 410, // Gone + 429, // TooManyRequests -> ES circuit breaker ]; export interface RetryableEsClientError { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index b81101879a7343..dbe61920b31b3e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -146,6 +146,11 @@ export interface RequestEntityTooLargeException { type: 'request_entity_too_large_exception'; } +export interface EsResponseTooLargeError { + type: 'es_response_too_large'; + contentLength: number; +} + /** @internal */ export interface AcknowledgeResponse { acknowledged: boolean; @@ -168,6 +173,7 @@ export interface ActionErrorTypeMap { index_not_green_timeout: IndexNotGreenTimeout; index_not_yellow_timeout: IndexNotYellowTimeout; cluster_shard_limit_exceeded: ClusterShardLimitExceeded; + es_response_too_large: EsResponseTooLargeError; } /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.test.ts index d2f7a3ab3c3d7d..181ae3d8b6a988 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.test.ts @@ -32,23 +32,54 @@ describe('readWithPit', () => { pitId: 'pitId', query: { match_all: {} }, batchSize: 10_000, + maxResponseSizeBytes: 100_000, })(); expect(client.search).toHaveBeenCalledTimes(1); - expect(client.search).toHaveBeenCalledWith({ - allow_partial_search_results: false, - pit: { - id: 'pitId', - keep_alive: '10m', - }, - query: { - match_all: {}, + expect(client.search).toHaveBeenCalledWith( + { + allow_partial_search_results: false, + pit: { + id: 'pitId', + keep_alive: '10m', + }, + query: { + match_all: {}, + }, + search_after: undefined, + seq_no_primary_term: undefined, + size: 10000, + sort: '_shard_doc:asc', + track_total_hits: true, }, - search_after: undefined, - seq_no_primary_term: undefined, - size: 10000, - sort: '_shard_doc:asc', - track_total_hits: true, + { maxResponseSize: 100_000 } + ); + }); + + it('returns left es_response_too_large when client throws RequestAbortedError', async () => { + // Create a mock client that rejects all methods with a RequestAbortedError + // response. + const retryableError = new EsErrors.RequestAbortedError( + 'The content length (536870889) is bigger than the maximum allow string (536870888)' + ); + const client = elasticsearchClientMock.createInternalClient( + elasticsearchClientMock.createErrorTransportRequestPromise(retryableError) + ); + + const task = readWithPit({ + client, + pitId: 'pitId', + query: { match_all: {} }, + batchSize: 10_000, + }); + try { + await task(); + } catch (e) { + /** ignore */ + } + await expect(task()).resolves.toEqual({ + _tag: 'Left', + left: { contentLength: 536870889, type: 'es_response_too_large' }, }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts index 1d0303947c1b64..91652f2175ff7d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts @@ -9,6 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { @@ -16,6 +17,7 @@ import { type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_PIT_KEEP_ALIVE } from './open_pit'; +import { EsResponseTooLargeError } from '.'; /** @internal */ export interface ReadWithPit { @@ -32,6 +34,7 @@ export interface ReadWithPitParams { batchSize: number; searchAfter?: number[]; seqNoPrimaryTerm?: boolean; + maxResponseSizeBytes?: number; } /* @@ -45,32 +48,39 @@ export const readWithPit = batchSize, searchAfter, seqNoPrimaryTerm, - }: ReadWithPitParams): TaskEither.TaskEither => + maxResponseSizeBytes, + }: ReadWithPitParams): TaskEither.TaskEither< + RetryableEsClientError | EsResponseTooLargeError, + ReadWithPit + > => () => { return client - .search({ - seq_no_primary_term: seqNoPrimaryTerm, - // Fail if the index being searched doesn't exist or is closed - // allow_no_indices: false, - // By default ES returns a 200 with partial results if there are shard - // request timeouts or shard failures which can lead to data loss for - // migrations - allow_partial_search_results: false, - // Sort fields are required to use searchAfter so we sort by the - // natural order of the index which is the most efficient option - // as order is not important for the migration - sort: '_shard_doc:asc', - pit: { id: pitId, keep_alive: DEFAULT_PIT_KEEP_ALIVE }, - size: batchSize, - search_after: searchAfter, - /** - * We want to know how many documents we need to process so we can log the progress. - * But we also want to increase the performance of these requests, - * so we ask ES to report the total count only on the first request (when searchAfter does not exist) - */ - track_total_hits: typeof searchAfter === 'undefined', - query, - }) + .search( + { + seq_no_primary_term: seqNoPrimaryTerm, + // Fail if the index being searched doesn't exist or is closed + // allow_no_indices: false, + // By default ES returns a 200 with partial results if there are shard + // request timeouts or shard failures which can lead to data loss for + // migrations + allow_partial_search_results: false, + // Sort fields are required to use searchAfter so we sort by the + // natural order of the index which is the most efficient option + // as order is not important for the migration + sort: '_shard_doc:asc', + pit: { id: pitId, keep_alive: DEFAULT_PIT_KEEP_ALIVE }, + size: batchSize, + search_after: searchAfter, + /** + * We want to know how many documents we need to process so we can log the progress. + * But we also want to increase the performance of these requests, + * so we ask ES to report the total count only on the first request (when searchAfter does not exist) + */ + track_total_hits: typeof searchAfter === 'undefined', + query, + }, + { maxResponseSize: maxResponseSizeBytes } + ) .then((body) => { const totalHits = typeof body.hits.total === 'number' @@ -93,5 +103,22 @@ export const readWithPit = totalHits, }); }) + .catch((e) => { + if ( + e instanceof EsErrors.RequestAbortedError && + /The content length \(\d+\) is bigger than the maximum/.test(e.message) + ) { + return Either.left({ + type: 'es_response_too_large' as const, + contentLength: Number.parseInt( + e.message.match(/The content length \((\d+)\) is bigger than the maximum/)?.[1] ?? + '-1', + 10 + ), + }); + } else { + throw e; + } + }) .catch(catchRetryableEsClientErrors); }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index 21b6e437d481ce..60b47ada7828ce 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -27,6 +27,7 @@ const migrationsConfig = { retryAttempts: 15, batchSize: 1000, maxBatchSizeBytes: ByteSizeValue.parse('100mb'), + maxReadBatchSizeBytes: ByteSizeValue.parse('500mb'), } as unknown as SavedObjectsMigrationConfigType; const createInitialStateCommonParams = { @@ -217,7 +218,9 @@ describe('createInitialState', () => { "knownTypes": Array [], "legacyIndex": ".kibana_task_manager", "logs": Array [], + "maxBatchSize": 1000, "maxBatchSizeBytes": 104857600, + "maxReadBatchSizeBytes": 524288000, "migrationDocLinks": Object { "clusterShardLimitExceeded": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#cluster-shard-limit-exceeded", "repeatedTimeoutRequests": "https://www.elastic.co/guide/en/kibana/test-branch/resolve-migrations-failures.html#_repeated_time_out_requests_that_eventually_fail", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts index 5f2e09c01db6dc..0b61d891619c47 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts @@ -126,7 +126,9 @@ export const createInitialState = ({ retryDelay: 0, retryAttempts: migrationsConfig.retryAttempts, batchSize: migrationsConfig.batchSize, + maxBatchSize: migrationsConfig.batchSize, maxBatchSizeBytes: migrationsConfig.maxBatchSizeBytes.getValueInBytes(), + maxReadBatchSizeBytes: migrationsConfig.maxReadBatchSizeBytes.getValueInBytes(), discardUnknownObjects: migrationsConfig.discardUnknownObjects === kibanaVersion, discardCorruptObjects: migrationsConfig.discardCorruptObjects === kibanaVersion, logs: [], diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index 18955bfc751aed..f9537a584881e8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -243,9 +243,9 @@ describe('KibanaMigrator', () => { const migrator = new KibanaMigrator(options); migrator.prepareMigrations(); await expect(migrator.runMigrations()).rejects.toMatchInlineSnapshot(` - [Error: Unable to complete saved object migrations for the [.my-index] index. Error: Reindex failed with the following error: - {"_tag":"Some","value":{"type":"elasticsearch_exception","reason":"task failed with an error"}}] - `); + [Error: Unable to complete saved object migrations for the [.my-index] index. Error: Reindex failed with the following error: + {"_tag":"Some","value":{"type":"elasticsearch_exception","reason":"task failed with an error"}}] + `); expect(loggingSystemMock.collect(options.logger).error[0][0]).toMatchInlineSnapshot(` [Error: Reindex failed with the following error: {"_tag":"Some","value":{"type":"elasticsearch_exception","reason":"task failed with an error"}}] @@ -533,6 +533,7 @@ const mockOptions = () => { algorithm: 'v2', batchSize: 20, maxBatchSizeBytes: ByteSizeValue.parse('20mb'), + maxReadBatchSizeBytes: new ByteSizeValue(536870888), pollInterval: 20000, scrollDuration: '10m', skip: false, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts index 04ecf73e23872b..2f734b3a564875 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.test.ts @@ -51,6 +51,7 @@ describe('migrationsStateActionMachine', () => { algorithm: 'v2', batchSize: 1000, maxBatchSizeBytes: new ByteSizeValue(1e8), + maxReadBatchSizeBytes: new ByteSizeValue(536870888), pollInterval: 0, scrollDuration: '0s', skip: false, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index c8d8884c980cd5..e83601a49e1728 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -17,6 +17,7 @@ import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import type { AliasAction, FetchIndexResponse } from '../actions'; import type { BulkIndexOperationTuple } from './create_batches'; +import { OutdatedDocumentsSearchRead, ReindexSourceToTempRead } from '../state'; /** @internal */ export type Aliases = Partial>; @@ -285,3 +286,11 @@ export function getMigrationType({ */ export const getTempIndexName = (indexPrefix: string, kibanaVersion: string): string => `${indexPrefix}_${kibanaVersion}_reindex_temp`; + +/** Increase batchSize by 20% until a maximum of maxBatchSize */ +export const increaseBatchSize = ( + stateP: OutdatedDocumentsSearchRead | ReindexSourceToTempRead +) => { + const increasedBatchSize = Math.floor(stateP.batchSize * 1.2); + return increasedBatchSize > stateP.maxBatchSize ? stateP.maxBatchSize : increasedBatchSize; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index 969a7704e4e75a..892ca75aea2f0b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -86,7 +86,9 @@ describe('migrations v2 model', () => { retryDelay: 0, retryAttempts: 15, batchSize: 1000, + maxBatchSize: 1000, maxBatchSizeBytes: 1e8, + maxReadBatchSizeBytes: 1234, discardUnknownObjects: false, discardCorruptObjects: false, indexPrefix: '.kibana', @@ -1832,6 +1834,8 @@ describe('migrations v2 model', () => { expect(newState.lastHitSortValue).toBe(lastHitSortValue); expect(newState.progress.processed).toBe(undefined); expect(newState.progress.total).toBe(1); + expect(newState.maxBatchSize).toBe(1000); + expect(newState.batchSize).toBe(1000); // don't increase batchsize above default expect(newState.logs).toMatchInlineSnapshot(` Array [ Object { @@ -1842,6 +1846,83 @@ describe('migrations v2 model', () => { `); }); + it('REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_TRANSFORM increases batchSize if < maxBatchSize', () => { + const outdatedDocuments = [{ _id: '1', _source: { type: 'vis' } }]; + const lastHitSortValue = [123456]; + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_READ'> = Either.right({ + outdatedDocuments, + lastHitSortValue, + totalHits: 1, + processedDocs: 1, + }); + let newState = model({ ...state, batchSize: 500 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(600); + newState = model({ ...state, batchSize: 600 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(720); + newState = model({ ...state, batchSize: 720 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(864); + newState = model({ ...state, batchSize: 864 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(1000); // + 20% would have been 1036 + expect(newState.controlState).toBe('REINDEX_SOURCE_TO_TEMP_TRANSFORM'); + expect(newState.maxBatchSize).toBe(1000); + }); + + it('REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_READ if left es_response_too_large', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_READ'> = Either.left({ + type: 'es_response_too_large', + contentLength: 4567, + }); + const newState = model(state, res) as ReindexSourceToTempRead; + expect(newState.controlState).toBe('REINDEX_SOURCE_TO_TEMP_READ'); + expect(newState.lastHitSortValue).toBe(undefined); // lastHitSortValue should not be set + expect(newState.progress.processed).toBe(undefined); // don't increment progress + expect(newState.batchSize).toBe(500); // halves the batch size + expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged + expect(newState.logs).toMatchInlineSnapshot(` + Array [ + Object { + "level": "warning", + "message": "Read a batch with a response content length of 4567 bytes which exceeds migrations.maxReadBatchSizeBytes, retrying by reducing the batch size in half to 500.", + }, + ] + `); + }); + + it('REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_READ if left es_response_too_large will not reduce batch size below 1', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_READ'> = Either.left({ + type: 'es_response_too_large', + contentLength: 2345, + }); + const newState = model({ ...state, batchSize: 1.5 }, res) as ReindexSourceToTempRead; + expect(newState.controlState).toBe('REINDEX_SOURCE_TO_TEMP_READ'); + expect(newState.lastHitSortValue).toBe(undefined); // lastHitSortValue should not be set + expect(newState.progress.processed).toBe(undefined); // don't increment progress + expect(newState.batchSize).toBe(1); // don't halve the batch size or go below 1 + expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged + expect(newState.logs).toMatchInlineSnapshot(` + Array [ + Object { + "level": "warning", + "message": "Read a batch with a response content length of 2345 bytes which exceeds migrations.maxReadBatchSizeBytes, retrying by reducing the batch size in half to 1.", + }, + ] + `); + }); + + it('REINDEX_SOURCE_TO_TEMP_READ -> FATAL if left es_response_too_large and batchSize already 1', () => { + const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_READ'> = Either.left({ + type: 'es_response_too_large', + contentLength: 2345, + }); + const newState = model({ ...state, batchSize: 1 }, res) as FatalState; + expect(newState.controlState).toBe('FATAL'); + expect(newState.batchSize).toBe(1); // don't halve the batch size or go below 1 + expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged + expect(newState.reason).toMatchInlineSnapshot( + `"After reducing the read batch size to a single document, the Elasticsearch response content length was 2345bytes which still exceeded migrations.maxReadBatchSizeBytes. Increase migrations.maxReadBatchSizeBytes and try again."` + ); + }); + it('REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_CLOSE_PIT if no outdated documents to reindex', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_READ'> = Either.right({ outdatedDocuments: [], @@ -2304,6 +2385,8 @@ describe('migrations v2 model', () => { expect(newState.lastHitSortValue).toBe(lastHitSortValue); expect(newState.progress.processed).toBe(undefined); expect(newState.progress.total).toBe(10); + expect(newState.maxBatchSize).toBe(1000); + expect(newState.batchSize).toBe(1000); // don't increase batchsize above default expect(newState.logs).toMatchInlineSnapshot(` Array [ Object { @@ -2345,6 +2428,83 @@ describe('migrations v2 model', () => { `); }); + it('OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_TRANSFORM increases batchSize up to maxBatchSize', () => { + const outdatedDocuments = [{ _id: '1', _source: { type: 'vis' } }]; + const lastHitSortValue = [123456]; + const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_READ'> = Either.right({ + outdatedDocuments, + lastHitSortValue, + totalHits: 1, + processedDocs: [], + }); + let newState = model({ ...state, batchSize: 500 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(600); + newState = model({ ...state, batchSize: 600 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(720); + newState = model({ ...state, batchSize: 720 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(864); + newState = model({ ...state, batchSize: 864 }, res) as ReindexSourceToTempTransform; + expect(newState.batchSize).toBe(1000); // + 20% would have been 1036 + expect(newState.controlState).toBe('OUTDATED_DOCUMENTS_TRANSFORM'); + expect(newState.maxBatchSize).toBe(1000); + }); + + it('OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_READ if left es_response_too_large', () => { + const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_READ'> = Either.left({ + type: 'es_response_too_large', + contentLength: 3456, + }); + const newState = model(state, res) as ReindexSourceToTempRead; + expect(newState.controlState).toBe('OUTDATED_DOCUMENTS_SEARCH_READ'); + expect(newState.lastHitSortValue).toBe(undefined); // lastHitSortValue should not be set + expect(newState.progress.processed).toBe(undefined); // don't increment progress + expect(newState.batchSize).toBe(500); // halves the batch size + expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged + expect(newState.logs).toMatchInlineSnapshot(` + Array [ + Object { + "level": "warning", + "message": "Read a batch with a response content length of 3456 bytes which exceeds migrations.maxReadBatchSizeBytes, retrying by reducing the batch size in half to 500.", + }, + ] + `); + }); + + it('OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_READ if left es_response_too_large will not reduce batch size below 1', () => { + const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_READ'> = Either.left({ + type: 'es_response_too_large', + contentLength: 2345, + }); + const newState = model({ ...state, batchSize: 1.5 }, res) as ReindexSourceToTempRead; + expect(newState.controlState).toBe('OUTDATED_DOCUMENTS_SEARCH_READ'); + expect(newState.lastHitSortValue).toBe(undefined); // lastHitSortValue should not be set + expect(newState.progress.processed).toBe(undefined); // don't increment progress + expect(newState.batchSize).toBe(1); // don't halve the batch size or go below 1 + expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged + expect(newState.logs).toMatchInlineSnapshot(` + Array [ + Object { + "level": "warning", + "message": "Read a batch with a response content length of 2345 bytes which exceeds migrations.maxReadBatchSizeBytes, retrying by reducing the batch size in half to 1.", + }, + ] + `); + }); + + it('OUTDATED_DOCUMENTS_SEARCH_READ -> FATAL if left es_response_too_large and batchSize already 1', () => { + const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_READ'> = Either.left({ + type: 'es_response_too_large', + contentLength: 2345, + }); + const newState = model({ ...state, batchSize: 1 }, res) as FatalState; + expect(newState.controlState).toBe('FATAL'); + expect(newState.batchSize).toBe(1); // don't halve the batch size or go below 1 + expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged + expect(newState.reason).toMatchInlineSnapshot( + `"After reducing the read batch size to a single document, the response content length was 2345 bytes which still exceeded migrations.maxReadBatchSizeBytes. Increase migrations.maxReadBatchSizeBytes and try again."` + ); + }); + it('OUTDATED_DOCUMENTS_SEARCH_READ -> OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT if no outdated documents to transform', () => { const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_READ'> = Either.right({ outdatedDocuments: [], diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 54657310912f8c..fdeb2d346c2d6a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -43,6 +43,7 @@ import { versionMigrationCompleted, buildRemoveAliasActions, MigrationType, + increaseBatchSize, } from './helpers'; import { buildTempIndexMap, createBatches } from './create_batches'; import type { MigrationLog } from '../types'; @@ -833,6 +834,8 @@ export const model = (currentState: State, resW: ResponseType): lastHitSortValue: res.right.lastHitSortValue, progress, logs, + // We succeeded in reading this batch, so increase the batch size for the next request. + batchSize: increaseBatchSize(stateP), }; } else { // we don't have any more outdated documents and need to either fail or move on to updating the target mappings. @@ -875,7 +878,32 @@ export const model = (currentState: State, resW: ResponseType): }; } } else { - throwBadResponse(stateP, res); + const left = res.left; + if (isTypeof(left, 'es_response_too_large')) { + if (stateP.batchSize === 1) { + return { + ...stateP, + controlState: 'FATAL', + reason: `After reducing the read batch size to a single document, the Elasticsearch response content length was ${left.contentLength}bytes which still exceeded migrations.maxReadBatchSizeBytes. Increase migrations.maxReadBatchSizeBytes and try again.`, + }; + } else { + const batchSize = Math.max(Math.floor(stateP.batchSize / 2), 1); + return { + ...stateP, + batchSize, + controlState: 'REINDEX_SOURCE_TO_TEMP_READ', + logs: [ + ...stateP.logs, + { + level: 'warning', + message: `Read a batch with a response content length of ${left.contentLength} bytes which exceeds migrations.maxReadBatchSizeBytes, retrying by reducing the batch size in half to ${batchSize}.`, + }, + ], + }; + } + } else { + throwBadResponse(stateP, left); + } } } else if (stateP.controlState === 'REINDEX_SOURCE_TO_TEMP_CLOSE_PIT') { const res = resW as ExcludeRetryableEsError>; @@ -1139,6 +1167,8 @@ export const model = (currentState: State, resW: ResponseType): lastHitSortValue: res.right.lastHitSortValue, progress, logs, + // We succeeded in reading this batch, so increase the batch size for the next request. + batchSize: increaseBatchSize(stateP), }; } else { // we don't have any more outdated documents and need to either fail or move on to updating the target mappings. @@ -1179,7 +1209,32 @@ export const model = (currentState: State, resW: ResponseType): }; } } else { - throwBadResponse(stateP, res); + const left = res.left; + if (isTypeof(left, 'es_response_too_large')) { + if (stateP.batchSize === 1) { + return { + ...stateP, + controlState: 'FATAL', + reason: `After reducing the read batch size to a single document, the response content length was ${left.contentLength} bytes which still exceeded migrations.maxReadBatchSizeBytes. Increase migrations.maxReadBatchSizeBytes and try again.`, + }; + } else { + const batchSize = Math.max(Math.floor(stateP.batchSize / 2), 1); + return { + ...stateP, + batchSize, + controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ', + logs: [ + ...stateP.logs, + { + level: 'warning', + message: `Read a batch with a response content length of ${left.contentLength} bytes which exceeds migrations.maxReadBatchSizeBytes, retrying by reducing the batch size in half to ${batchSize}.`, + }, + ], + }; + } + } else { + throwBadResponse(stateP, left); + } } } else if (stateP.controlState === 'OUTDATED_DOCUMENTS_TRANSFORM') { const res = resW as ExcludeRetryableEsError>; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 2ed66ad9933e30..1b5a9fe99fe3a7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -219,6 +219,7 @@ export const nextActionMap = ( query: state.outdatedDocumentsQuery, batchSize: state.batchSize, searchAfter: state.lastHitSortValue, + maxResponseSizeBytes: state.maxReadBatchSizeBytes, }), OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT: (state: OutdatedDocumentsSearchClosePit) => Actions.closePit({ client, pitId: state.pitId }), diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index 6a483da04a3997..8a6be0269947e5 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -63,7 +63,6 @@ export interface BaseState extends ControlState { * max_retry_time = 11.7 minutes */ readonly retryAttempts: number; - /** * The number of documents to process in each batch. This determines the * maximum number of documents that will be read and written in a single @@ -83,6 +82,12 @@ export interface BaseState extends ControlState { * When writing batches, we limit the number of documents in a batch * (batchSize) as well as the size of the batch in bytes (maxBatchSizeBytes). */ + readonly maxBatchSize: number; + /** + * The number of documents to process in each batch. Under most circumstances + * batchSize == maxBatchSize. But if we fail to read a batch because of a + * Nodejs `RangeError` we'll temporarily half `batchSize` and retry. + */ readonly batchSize: number; /** * When writing batches, limits the batch size in bytes to ensure that we @@ -90,6 +95,12 @@ export interface BaseState extends ControlState { * http.max_content_length which defaults to 100mb. */ readonly maxBatchSizeBytes: number; + /** + * If a read batch exceeds this limit we half the batchSize and retry. By + * not JSON.parsing and transforming large batches we can avoid RangeErrors + * or Kibana OOMing. + */ + readonly maxReadBatchSizeBytes: number; readonly logs: MigrationLog[]; /** * If saved objects exist which have an unknown type they will cause diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.ts index 89aa1a13fa928d..69722cb9652e86 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/test_helpers/context.ts @@ -31,6 +31,7 @@ export const createMigrationConfigMock = ( algorithm: 'zdt', batchSize: 1000, maxBatchSizeBytes: new ByteSizeValue(1e8), + maxReadBatchSizeBytes: new ByteSizeValue(1e6), pollInterval: 0, scrollDuration: '0s', skip: false, diff --git a/packages/kbn-cypress-config/index.ts b/packages/kbn-cypress-config/index.ts index 58abd54a56f69d..6f0c23e580fdd4 100644 --- a/packages/kbn-cypress-config/index.ts +++ b/packages/kbn-cypress-config/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { v4 as uuid } from 'uuid'; import { defineConfig } from 'cypress'; import wp from '@cypress/webpack-preprocessor'; @@ -15,9 +16,12 @@ export function defineCypressConfig(options?: Cypress.ConfigOptions) { e2e: { ...options?.e2e, setupNodeEvents(on, config) { - on( - 'file:preprocessor', - wp({ + on('file:preprocessor', (file) => { + const id = uuid(); + // Fix an issue with running Cypress parallel + file.outputPath = file.outputPath.replace(/^(.*\/)(.*?)(\..*)$/, `$1$2.${id}$3`); + + return wp({ webpackOptions: { resolve: { extensions: ['.ts', '.tsx', '.js'], @@ -39,8 +43,8 @@ export function defineCypressConfig(options?: Cypress.ConfigOptions) { ], }, }, - }) - ); + })(file); + }); const external = options?.e2e?.setupNodeEvents; if (external) { diff --git a/packages/kbn-dev-proc-runner/src/with_proc_runner.ts b/packages/kbn-dev-proc-runner/src/with_proc_runner.ts index 5c9bc8103bc62f..3aa0702e17b432 100644 --- a/packages/kbn-dev-proc-runner/src/with_proc_runner.ts +++ b/packages/kbn-dev-proc-runner/src/with_proc_runner.ts @@ -19,9 +19,9 @@ import { ProcRunner } from './proc_runner'; * @param {async Function} fn * @return {Promise} */ -export async function withProcRunner( +export async function withProcRunner( log: ToolingLog, - fn: (procs: ProcRunner) => Promise + fn: (procs: ProcRunner) => Promise ): Promise { const procs = new ProcRunner(log); diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index 8e0cb4cfc703fd..f779803d794b1b 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -14,6 +14,7 @@ export { startServersCli, startServers } from './src/functional_tests/start_serv // @internal export { runTestsCli, runTests } from './src/functional_tests/run_tests'; +export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; export type { diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 6d5dc75b8d969b..fbad913dc75fa3 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -192,6 +192,7 @@ export const schema = Joi.object() elasticsearch: urlPartsSchema({ requiredKeys: ['port'], }), + fleetserver: urlPartsSchema(), }) .default(), diff --git a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts index 24f3eb9a527b21..b8f6fc4714273f 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts +++ b/packages/kbn-test/src/functional_tests/lib/run_elasticsearch.ts @@ -19,6 +19,7 @@ interface RunElasticsearchOptions { config: Config; onEarlyExit?: (msg: string) => void; logsDir?: string; + name?: string; } interface CcsConfig { @@ -63,13 +64,13 @@ function getEsConfig({ export async function runElasticsearch( options: RunElasticsearchOptions ): Promise<() => Promise> { - const { log, logsDir } = options; + const { log, logsDir, name } = options; const config = getEsConfig(options); if (!config.ccsConfig) { const node = await startEsNode({ log, - name: 'ftr', + name: name ?? 'ftr', logsDir, config, }); @@ -81,7 +82,7 @@ export async function runElasticsearch( const remotePort = await getPort(); const remoteNode = await startEsNode({ log, - name: 'ftr-remote', + name: name ?? 'ftr-remote', logsDir, config: { ...config, @@ -92,7 +93,7 @@ export async function runElasticsearch( const localNode = await startEsNode({ log, - name: 'ftr-local', + name: name ?? 'ftr-local', logsDir, config: { ...config, diff --git a/src/cli/command.js b/src/cli/command.js index b85037330027b8..936a8e9524fd50 100644 --- a/src/cli/command.js +++ b/src/cli/command.js @@ -53,30 +53,44 @@ Command.prototype.collectUnknownOptions = function () { this.allowUnknownOption(); this.getUnknownOptions = function () { const opts = {}; + + /** + * Commander.js already presents the unknown args split by "=", + * and shorthand switches already split, like -asd => -a, -s, -d + */ const unknowns = this.unknownArgv(); + const singleFlagArgs = unknowns.filter((flag) => { + return flag.match(/^-[a-zA-Z0-9]$/); + }); + + if (singleFlagArgs.length) { + this.error( + `${title} shouldn't have unknown shorthand flag arguments (${singleFlagArgs}). Possibly an argumentation error?` + ); + } + while (unknowns.length) { - const opt = unknowns.shift().split('='); - if (opt[0].slice(0, 2) !== '--') { - this.error(`${title} "${opt[0]}" must start with "--"`); - } + const optName = unknowns.shift(); - if (opt.length === 1) { - if (!unknowns.length || unknowns[0][0] === '-') { - this.error(`${title} "${opt[0]}" must have a value`); - } + if (optName.slice(0, 2) !== '--') { + this.error(`${title} "${optName}" must start with "--"`); + } - opt.push(unknowns.shift()); + if (unknowns.length === 0) { + this.error(`${title} "${optName}" must have a value`); } - let val = opt[1]; + const optValue = unknowns.shift(); + + let val = optValue; try { - val = JSON.parse(opt[1]); - } catch (e) { - val = opt[1]; + val = JSON.parse(optValue); + } catch { + val = optValue; } - set(opts, opt[0].slice(2), val); + set(opts, optName.slice(2), val); } return opts; @@ -96,7 +110,7 @@ Command.prototype.action = _.wrap(Command.prototype.action, function (action, fn const ret = fn.apply(this, args); if (ret && typeof ret.then === 'function') { ret.then(null, function (e) { - console.log('FATAL CLI ERROR', e.stack); + console.log('FATAL CLI ERROR', e.stack); process.exit(1); }); } diff --git a/src/cli/command.test.js b/src/cli/command.test.js new file mode 100644 index 00000000000000..f00a0506866fd9 --- /dev/null +++ b/src/cli/command.test.js @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import commander from 'commander'; + +import './command'; + +describe('Commander extensions', () => { + const mockExit = jest.fn((exitCode) => { + // Prevent exiting from shell, let's throw something instead. + throw new Error('Exit ' + exitCode); + }); + + beforeAll(() => { + process._real_exit = process.exit; + + process.exit = mockExit; + }); + + afterAll(() => { + process.exit = process._real_exit; + }); + + describe('regular options', () => { + it('collects multiple args', () => { + const args = makeArgs('test1', '--code', 'xoxo', '--param', 123); + + const { collectedOptions, unknownOptions } = parseArgs('test1', ['code', 'param'], args); + + expect(collectedOptions).toEqual({ code: 'xoxo', param: 123 }); + expect(unknownOptions).toEqual({}); + }); + }); + + describe('unknown args', () => { + it('will be collected besides defined ones', () => { + const args = makeArgs('test2', '--code', 'xoxo', '--specialConfig', 'foobar'); + + const { collectedOptions, unknownOptions } = parseArgs('test2', ['code'], args); + + expect(collectedOptions).toEqual({ code: 'xoxo' }); + expect(unknownOptions).toEqual({ specialConfig: 'foobar' }); + }); + + it('will be collected with dashes, when quoted', () => { + const args = makeArgs('test3', '--code', 'xoxo', '--specialConfig', '"---foobar"'); + + const { collectedOptions, unknownOptions } = parseArgs('test3', ['code'], args); + + expect(collectedOptions).toEqual({ code: 'xoxo' }); + expect(unknownOptions).toEqual({ specialConfig: '---foobar' }); + }); + + it('will be collected with dashes as compound option', () => { + const args = makeArgs('test4', '--code', 'xoxo', '--specialConfig=----foobar'); + + const { collectedOptions, unknownOptions } = parseArgs('test4', ['code'], args); + + expect(collectedOptions).toEqual({ code: 'xoxo' }); + expect(unknownOptions).toEqual({ specialConfig: '----foobar' }); + }); + + it('will crash if they contain bool flags', () => { + const args = makeArgs('test5', '--code', 'xoxo', '--secretOpt', '1234', '--useSpecial'); + + expect(() => parseArgs('test5', ['code'], args)).toThrow(/Exit/); + + expect(mockExit).toHaveBeenCalledWith(64); + }); + + it('will crash if they contain shorthand flags', () => { + const args = makeArgs('test6', '--code', 'xoxo', '-xvz'); + + expect(() => parseArgs('test6', ['code'], args)).toThrow(/Exit/); + + expect(mockExit).toHaveBeenCalledWith(64); + }); + }); +}); + +function prepareProgram(testName, options) { + const optionsDefinitions = options.map((optionName) => { + const short = optionName[0].toLowerCase(); + return `-${short}, --${optionName} <${optionName}>`; + }); + + let command = commander.command(testName); + + for (const option of optionsDefinitions) { + command = command.option(option); + } + + return command; +} + +function parseArgs(testName, options, args) { + let storedActionArgs = null; + + const command = prepareProgram(testName, options) + .collectUnknownOptions() + .action((command) => { + const colllectedOptions = {}; + + options.forEach((opt) => { + colllectedOptions[opt] = command[opt]; + }); + + storedActionArgs = colllectedOptions; + }); + + // Pass the args at the root command + commander.parse(args); + + return { + collectedOptions: storedActionArgs, + unknownOptions: command.getUnknownOptions(), + command, + }; +} + +function makeArgs(...args) { + return ['node', 'test.js', ...args]; +} diff --git a/src/cli/serve/integration_tests/serverless_config_flag.test.ts b/src/cli/serve/integration_tests/serverless_config_flag.test.ts index d603eff2eaec4d..46c94752f40daa 100644 --- a/src/cli/serve/integration_tests/serverless_config_flag.test.ts +++ b/src/cli/serve/integration_tests/serverless_config_flag.test.ts @@ -51,7 +51,7 @@ describe('cli serverless project type', () => { expect(error).toBe(undefined); expect(stdout.toString('utf8')).toContain( - 'FATAL CLI ERROR Error: invalid --serverless value, must be one of es, oblt, security' + 'FATAL CLI ERROR Error: invalid --serverless value, must be one of es, oblt, security' ); expect(status).toBe(1); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index 8bf85d5c04c660..38d4075f9516c0 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -22,6 +22,7 @@ import { type OpenPitResponse, reindex, readWithPit, + type EsResponseTooLargeError, type ReadWithPit, setWriteBlock, updateAliases, @@ -87,6 +88,7 @@ describe('migration actions', () => { { _source: { title: 'doc 3' } }, { _source: { title: 'saved object 4', type: 'another_unused_type' } }, { _source: { title: 'f-agent-event 5', type: 'f_agent_event' } }, + { _source: { title: new Array(1000).fill('a').join(), type: 'large' } }, // "large" saved object ] as unknown as SavedObjectsRawDoc[]; await bulkOverwriteTransformedDocuments({ client, @@ -727,6 +729,7 @@ describe('migration actions', () => { expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) .toMatchInlineSnapshot(` Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", "doc 1", "doc 2", "doc 3", @@ -763,6 +766,7 @@ describe('migration actions', () => { expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) .toMatchInlineSnapshot(` Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", "doc 1", "doc 2", "doc 3", @@ -792,6 +796,7 @@ describe('migration actions', () => { expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) .toMatchInlineSnapshot(` Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", "doc 1_updated", "doc 2_updated", "doc 3_updated", @@ -843,6 +848,7 @@ describe('migration actions', () => { expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) .toMatchInlineSnapshot(` Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", "doc 1_updated", "doc 2_updated", "doc 3_updated", @@ -893,6 +899,7 @@ describe('migration actions', () => { expect((results.hits?.hits as SavedObjectsRawDoc[]).map((doc) => doc._source.title).sort()) .toMatchInlineSnapshot(` Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a_updated", "doc 1", "doc 2", "doc 3_updated", @@ -1119,7 +1126,7 @@ describe('migration actions', () => { }); const docsResponse = (await readWithPitTask()) as Either.Right; - await expect(docsResponse.right.outdatedDocuments.length).toBe(5); + await expect(docsResponse.right.outdatedDocuments.length).toBe(6); }); it('requests the batchSize of documents from an index', async () => { @@ -1170,6 +1177,7 @@ describe('migration actions', () => { expect(docsResponse.right.outdatedDocuments.map((doc) => doc._source.title).sort()) .toMatchInlineSnapshot(` Array [ + "a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a", "doc 1", "doc 2", "doc 3", @@ -1256,6 +1264,36 @@ describe('migration actions', () => { ); }); + it('returns a left es_response_too_large error when a read batch exceeds the maxResponseSize', async () => { + const openPitTask = openPit({ client, index: 'existing_index_with_docs' }); + const pitResponse = (await openPitTask()) as Either.Right; + + let readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 1, // small batch size so we don't exceed the maxResponseSize + searchAfter: undefined, + maxResponseSizeBytes: 500, // set a small size to force the error + }); + const rightResponse = (await readWithPitTask()) as Either.Right; + + await expect(Either.isRight(rightResponse)).toBe(true); + + readWithPitTask = readWithPit({ + client, + pitId: pitResponse.right.pitId, + query: { match_all: {} }, + batchSize: 10, // a bigger batch will exceed the maxResponseSize + searchAfter: undefined, + maxResponseSizeBytes: 500, // set a small size to force the error + }); + const leftResponse = (await readWithPitTask()) as Either.Left; + + expect(leftResponse.left.type).toBe('es_response_too_large'); + expect(leftResponse.left.contentLength).toBe(3184); + }); + it('rejects if PIT does not exist', async () => { const readWithPitTask = readWithPit({ client, diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts index 537655cf5450ca..a5a10cd05e5743 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/dot_kibana_split.test.ts @@ -60,6 +60,7 @@ describe('split .kibana index into multiple system indices', () => { beforeAll(async () => { esServer = await startElasticsearch({ dataArchive: Path.join(__dirname, '..', 'archives', '7.3.0_xpack_sample_saved_objects.zip'), + timeout: 60000, }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.test.ts new file mode 100644 index 00000000000000..0fce643975c533 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/read_batch_size.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import fs from 'fs/promises'; +import { Root } from '@kbn/core-root-server-internal'; +import { + createRootWithCorePlugins, + type TestElasticsearchUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { delay } from '../test_utils'; +import { startElasticsearch } from '../kibana_migrator_test_kit'; + +const logFilePath = Path.join(__dirname, 'read_batch_size.log'); + +describe('migration v2 - read batch size', () => { + let esServer: TestElasticsearchUtils; + let root: Root; + let logs: string; + + beforeEach(async () => { + esServer = await startElasticsearch({ + dataArchive: Path.join(__dirname, '..', 'archives', '8.4.0_with_sample_data_logs.zip'), + }); + await fs.unlink(logFilePath).catch(() => {}); + }); + + afterEach(async () => { + await root?.shutdown(); + await esServer?.stop(); + await delay(10); + }); + + it('reduces the read batchSize in half if a batch exceeds maxReadBatchSizeBytes', async () => { + root = createRoot({ maxReadBatchSizeBytes: 15000 }); + await root.preboot(); + await root.setup(); + await root.start(); + + // Check for migration steps present in the logs + logs = await fs.readFile(logFilePath, 'utf-8'); + + expect(logs).toMatch( + /Read a batch with a response content length of \d+ bytes which exceeds migrations\.maxReadBatchSizeBytes, retrying by reducing the batch size in half to 15/ + ); + expect(logs).toMatch('[.kibana] Migration completed'); + }); + + it('does not reduce the read batchSize in half if no batches exceeded maxReadBatchSizeBytes', async () => { + root = createRoot({ maxReadBatchSizeBytes: 50000 }); + await root.preboot(); + await root.setup(); + await root.start(); + + // Check for migration steps present in the logs + logs = await fs.readFile(logFilePath, 'utf-8'); + + expect(logs).not.toMatch('retrying by reducing the batch size in half to'); + expect(logs).toMatch('[.kibana] Migration completed'); + }); +}); + +function createRoot({ maxReadBatchSizeBytes }: { maxReadBatchSizeBytes?: number }) { + return createRootWithCorePlugins( + { + migrations: { + maxReadBatchSizeBytes, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + level: 'info', + appenders: ['file'], + }, + ], + }, + }, + { + oss: false, + } + ); +} diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index f354430261c3d6..43d5cc746a7e35 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -87,12 +87,14 @@ export interface KibanaMigratorTestKit { export const startElasticsearch = async ({ basePath, dataArchive, + timeout, }: { basePath?: string; dataArchive?: string; + timeout?: number; } = {}) => { const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), + adjustTimeout: (t: number) => jest.setTimeout(t + (timeout ?? 0)), settings: { es: { license: 'basic', diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_item_sets.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_item_sets.ts index 3469384f508d04..ccb237314c1258 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_item_sets.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_item_sets.ts @@ -57,7 +57,7 @@ export function getShouldClauses(significantTerms: SignificantTerm[]) { export function getFrequentItemSetsAggFields(significantTerms: SignificantTerm[]) { return Array.from( group(significantTerms, ({ fieldName }) => fieldName), - ([field, values]) => ({ field, include: values.map((d) => d.fieldValue) }) + ([field, values]) => ({ field, include: values.map((d) => String(d.fieldValue)) }) ); } @@ -100,7 +100,6 @@ export async function fetchFrequentItemSets( const frequentItemSetsAgg: Record = { fi: { - // @ts-expect-error `frequent_item_sets` is not yet part of `AggregationsAggregationContainer` frequent_item_sets: { minimum_set_size: 2, size: 200, diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index 88d4408d331523..1ade523356987e 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -79,8 +79,8 @@ describe('Storage Explorer', () => { it('has a list of summary stats', () => { cy.contains('Total APM size'); - cy.contains('Disk space used'); - cy.contains('Incremental APM size'); + cy.contains('Relative disk space used'); + cy.contains('Delta in APM size'); cy.contains('Daily data generation'); cy.contains('Traces per minute'); cy.contains('Number of services'); diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts b/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts index 1736cd852a4177..ce8b12760ab2f5 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts @@ -7,9 +7,12 @@ import { CoreStart } from '@kbn/core-lifecycle-browser'; -export function getIndexManagementHref(core: CoreStart) { +export function getIndexManagementHref(core: CoreStart, dataStream?: string) { + const indexManagementPath = '/data/index_management/data_streams'; return core.application.getUrlForApp('management', { - path: '/data/index_management/data_streams', + path: dataStream + ? `${indexManagementPath}/${dataStream}?isDeepLink=true` + : indexManagementPath, }); } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx index 4c1b283f056289..73a79fcc0a020f 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx @@ -164,7 +164,7 @@ export function TipsAndResources() { > {cards.map(({ icon, title, description, href }) => ( - + } title={title} diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx index c16dc5388cc2ff..b40102e0429bf8 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx @@ -16,10 +16,16 @@ import { EuiIcon, EuiProgress, EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { apmServiceInventoryOptimizedSorting } from '@kbn/observability-plugin/common'; +import moment from 'moment'; +import { isEmpty } from 'lodash'; +import { downloadJson } from '../../../../utils/download_json'; import { AgentName } from '../../../../../typings/es_schemas/ui/fields/agent'; import { EnvironmentBadge } from '../../../shared/environment_badge'; import { asPercent } from '../../../../../common/utils/formatters'; @@ -218,7 +224,7 @@ export function ServicesTable() { {i18n.translate( 'xpack.apm.storageExplorer.table.samplingColumnName', { - defaultMessage: 'Sample rate', + defaultMessage: 'Sampling rate', } )}{' '} {loading && } + + + + downloadJson({ + fileName: `storage-explorefpr-${moment(Date.now()).format( + 'YYYYMMDDHHmmss' + )}.json`, + data: { + filters: { + rangeFrom, + rangeTo, + environment, + kuery, + indexLifecyclePhase, + }, + services: serviceStatisticsItems.map((item) => ({ + ...item, + sampling: asPercent(item?.sampling, 1), + size: item?.size + ? asDynamicBytes(item?.size) + : NOT_AVAILABLE_LABEL, + })), + }, + }) + } + fill + > + {i18n.translate('xpack.apm.storageExplorer.downloadReport', { + defaultMessage: 'Download report', + })} + + + + ['indicesStats']; @@ -33,6 +36,8 @@ interface Props { } export function IndexStatsPerService({ indicesStats, status }: Props) { + const { core } = useApmPluginContext(); + const columns: Array< EuiBasicTableColumn> > = [ @@ -84,7 +89,15 @@ export function IndexStatsPerService({ indicesStats, status }: Props) { defaultMessage: 'Data stream', } ), - render: (_, { dataStream }) => dataStream ?? NOT_AVAILABLE_LABEL, + render: (_, { dataStream }) => + ( + + {dataStream} + + ) ?? NOT_AVAILABLE_LABEL, sortable: true, }, { diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx index 860a9b251b71ea..0021af524b3b70 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx @@ -104,7 +104,7 @@ export function SummaryStats() { 'xpack.apm.storageExplorer.summary.totalSize.tooltip', { defaultMessage: - 'Total storage size of all the APM indices currently, ignoring all filters.', + 'Total storage size of all APM indices including replicas, ignoring the filter settings.', } )} value={asDynamicBytes(data?.totalSize)} @@ -115,7 +115,7 @@ export function SummaryStats() { label={i18n.translate( 'xpack.apm.storageExplorer.summary.diskSpaceUsedPct', { - defaultMessage: 'Disk space used', + defaultMessage: 'Relative disk space used', } )} tooltipContent={i18n.translate( @@ -131,13 +131,13 @@ export function SummaryStats() { /> {isLabsButtonEnabled && } - {isStorageExplorerAvailable && ( - - - - {i18n.translate('xpack.apm.storageExplorerLinkLabel', { - defaultMessage: 'Storage Explorer', - })} - - - - )} - {canCreateMlJobs && } {isAlertingAvailable && ( - - -

- {i18n.translate('xpack.apm.views.storageExplorer.title', { - defaultMessage: 'Storage explorer', - })} -

-
-
- - - -
- ), + pageTitle: i18n.translate('xpack.apm.views.storageExplorer.title', { + defaultMessage: 'Storage explorer', + }), rightSideItems: [ { } }, }, + { + label: apmStorageExplorerTitle, + app: 'apm', + path: '/storage-explorer', + }, ], }, ]; diff --git a/x-pack/plugins/apm/public/utils/download_json.ts b/x-pack/plugins/apm/public/utils/download_json.ts new file mode 100644 index 00000000000000..2ce9b08f250385 --- /dev/null +++ b/x-pack/plugins/apm/public/utils/download_json.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function downloadJson({ + fileName, + data = {}, +}: { + fileName: string; + data?: Record; +}) { + const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( + JSON.stringify(data) + )}`; + const link = document.createElement('a'); + link.href = jsonString; + link.download = fileName; + link.click(); +} diff --git a/x-pack/plugins/cases/common/api/cases/comment/index.test.ts b/x-pack/plugins/cases/common/api/cases/comment/index.test.ts index 5e3a56fc1b077b..b0f99125226c30 100644 --- a/x-pack/plugins/cases/common/api/cases/comment/index.test.ts +++ b/x-pack/plugins/cases/common/api/cases/comment/index.test.ts @@ -28,7 +28,6 @@ import { BulkCreateCommentRequestRt, BulkGetAttachmentsRequestRt, BulkGetAttachmentsResponseRt, - FindCommentsArgsRt, } from '.'; describe('Comments', () => { @@ -750,35 +749,6 @@ describe('Comments', () => { }); }); - describe('FindCommentsArgsRt', () => { - const defaultRequest = { - caseID: 'basic-case-id', - queryParams: { - page: 1, - perPage: 10, - sortOrder: 'asc', - }, - }; - - it('has expected attributes in request', () => { - const query = FindCommentsArgsRt.decode(defaultRequest); - - expect(query).toStrictEqual({ - _tag: 'Right', - right: defaultRequest, - }); - }); - - it('removes foo:bar attributes from request', () => { - const query = FindCommentsArgsRt.decode({ ...defaultRequest, foo: 'bar' }); - - expect(query).toStrictEqual({ - _tag: 'Right', - right: defaultRequest, - }); - }); - }); - describe('BulkCreateCommentRequestRt', () => { const defaultRequest = [ { diff --git a/x-pack/plugins/cases/common/api/cases/comment/index.ts b/x-pack/plugins/cases/common/api/cases/comment/index.ts index 1cbe92ae70784b..573f36bd0f9632 100644 --- a/x-pack/plugins/cases/common/api/cases/comment/index.ts +++ b/x-pack/plugins/cases/common/api/cases/comment/index.ts @@ -298,13 +298,6 @@ export const FindCommentsQueryParamsRt = rt.exact( }) ); -export const FindCommentsArgsRt = rt.intersection([ - rt.strict({ - caseID: rt.string, - }), - rt.strict({ queryParams: rt.union([FindCommentsQueryParamsRt, rt.undefined]) }), -]); - export const BulkCreateCommentRequestRt = rt.array(CommentRequestRt); export const BulkGetAttachmentsRequestRt = rt.strict({ diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/response.ts b/x-pack/plugins/cases/common/api/cases/user_actions/response.ts index af9b15ddf44fe0..5fe505a7fa0157 100644 --- a/x-pack/plugins/cases/common/api/cases/user_actions/response.ts +++ b/x-pack/plugins/cases/common/api/cases/user_actions/response.ts @@ -61,7 +61,7 @@ const CaseUserActionBasicWithoutConnectorIdRt = rt.intersection([ UserActionCommonAttributesRt, ]); -const CaseUserActionDeprecatedResponseRt = rt.intersection([ +export const CaseUserActionDeprecatedResponseRt = rt.intersection([ CaseUserActionBasicRt, CaseUserActionInjectedDeprecatedIdsRt, ]); diff --git a/x-pack/plugins/cases/common/api/metrics/case.test.ts b/x-pack/plugins/cases/common/api/metrics/case.test.ts index 931970d90bcf54..073aac4899fb90 100644 --- a/x-pack/plugins/cases/common/api/metrics/case.test.ts +++ b/x-pack/plugins/cases/common/api/metrics/case.test.ts @@ -15,7 +15,6 @@ import { describe('Metrics case', () => { describe('SingleCaseMetricsRequestRt', () => { const defaultRequest = { - caseId: 'basic-case-id', features: ['alerts.count', 'lifespan'], }; diff --git a/x-pack/plugins/cases/common/api/metrics/case.ts b/x-pack/plugins/cases/common/api/metrics/case.ts index 7e7fb6a3f4a4c0..0e470a74bc4faf 100644 --- a/x-pack/plugins/cases/common/api/metrics/case.ts +++ b/x-pack/plugins/cases/common/api/metrics/case.ts @@ -73,10 +73,6 @@ const AlertUsersMetricsRt = rt.strict({ }); export const SingleCaseMetricsRequestRt = rt.strict({ - /** - * The ID of the case. - */ - caseId: rt.string, /** * The metrics to retrieve. */ diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts index d3b4a088f8d4ab..9e41d7e832461e 100644 --- a/x-pack/plugins/cases/server/client/attachments/add.ts +++ b/x-pack/plugins/cases/server/client/attachments/add.ts @@ -27,8 +27,6 @@ import { validateRegisteredAttachments } from './validators'; export const addComment = async (addArgs: AddArgs, clientArgs: CasesClientArgs): Promise => { const { comment, caseId } = addArgs; - const query = decodeWithExcessOrThrow(CommentRequestRt)(comment); - const { logger, authorization, @@ -36,8 +34,11 @@ export const addComment = async (addArgs: AddArgs, clientArgs: CasesClientArgs): externalReferenceAttachmentTypeRegistry, } = clientArgs; - decodeCommentRequest(comment, externalReferenceAttachmentTypeRegistry); try { + const query = decodeWithExcessOrThrow(CommentRequestRt)(comment); + + decodeCommentRequest(comment, externalReferenceAttachmentTypeRegistry); + const savedObjectID = SavedObjectsUtils.generateId(); await authorization.ensureAuthorized({ diff --git a/x-pack/plugins/cases/server/client/attachments/bulk_create.ts b/x-pack/plugins/cases/server/client/attachments/bulk_create.ts index 7dc6fc8fc54cf8..b8986b950008dc 100644 --- a/x-pack/plugins/cases/server/client/attachments/bulk_create.ts +++ b/x-pack/plugins/cases/server/client/attachments/bulk_create.ts @@ -20,19 +20,12 @@ import { Operations } from '../../authorization'; import type { BulkCreateArgs } from './types'; import { validateRegisteredAttachments } from './validators'; -/** - * Bulk create attachments to a case. - * - * @ignore - */ export const bulkCreate = async ( args: BulkCreateArgs, clientArgs: CasesClientArgs ): Promise => { const { attachments, caseId } = args; - decodeWithExcessOrThrow(BulkCreateCommentRequestRt)(attachments); - const { logger, authorization, @@ -40,16 +33,18 @@ export const bulkCreate = async ( persistableStateAttachmentTypeRegistry, } = clientArgs; - attachments.forEach((attachment) => { - decodeCommentRequest(attachment, externalReferenceAttachmentTypeRegistry); - validateRegisteredAttachments({ - query: attachment, - persistableStateAttachmentTypeRegistry, - externalReferenceAttachmentTypeRegistry, + try { + decodeWithExcessOrThrow(BulkCreateCommentRequestRt)(attachments); + + attachments.forEach((attachment) => { + decodeCommentRequest(attachment, externalReferenceAttachmentTypeRegistry); + validateRegisteredAttachments({ + query: attachment, + persistableStateAttachmentTypeRegistry, + externalReferenceAttachmentTypeRegistry, + }); }); - }); - try { const [attachmentsWithIds, entities]: [Array<{ id: string } & CommentRequest>, OwnerEntity[]] = attachments.reduce<[Array<{ id: string } & CommentRequest>, OwnerEntity[]]>( ([a, e], attachment) => { diff --git a/x-pack/plugins/cases/server/client/attachments/get.test.tsx b/x-pack/plugins/cases/server/client/attachments/get.test.ts similarity index 59% rename from x-pack/plugins/cases/server/client/attachments/get.test.tsx rename to x-pack/plugins/cases/server/client/attachments/get.test.ts index c289b4b8f6edb5..250cfa9f0b2524 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.test.tsx +++ b/x-pack/plugins/cases/server/client/attachments/get.test.ts @@ -18,9 +18,9 @@ describe('get', () => { it('Invalid total items results in error', async () => { await expect(() => - findComment({ caseID: 'mock-id', queryParams: { page: 2, perPage: 9001 } }, clientArgs) - ).rejects.toThrow( - 'The number of documents is too high. Paginating through more than 10,000 documents is not possible.' + findComment({ caseID: 'mock-id', findQueryParams: { page: 2, perPage: 9001 } }, clientArgs) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to find comments case id: mock-id: Error: The number of documents is too high. Paginating through more than 10,000 documents is not possible."` ); }); @@ -28,10 +28,12 @@ describe('get', () => { await expect( findComment( // @ts-expect-error: excess attribute - { caseID: 'mock-id', queryParams: { page: 2, perPage: 9001 }, foo: 'bar' }, + { caseID: 'mock-id', findQueryParams: { page: 2, perPage: 9001, foo: 'bar' } }, clientArgs ) - ).rejects.toThrow('invalid keys "foo"'); + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to find comments case id: mock-id: Error: invalid keys \\"foo\\""` + ); }); }); }); diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts index 522ed5804f0d71..62f647434e8904 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.ts +++ b/x-pack/plugins/cases/server/client/attachments/get.ts @@ -20,7 +20,7 @@ import type { FindCommentsArgs, GetAllAlertsAttachToCase, GetAllArgs, GetArgs } import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../../common/constants'; import { - FindCommentsArgsRt, + FindCommentsQueryParamsRt, CommentType, CommentsRt, CommentRt, @@ -112,7 +112,7 @@ export const getAllAlertsAttachToCase = async ( * Retrieves the attachments for a case entity. This support pagination. */ export async function find( - data: FindCommentsArgs, + { caseID, findQueryParams }: FindCommentsArgs, clientArgs: CasesClientArgs ): Promise { const { @@ -121,11 +121,11 @@ export async function find( authorization, } = clientArgs; - const { caseID, queryParams } = decodeWithExcessOrThrow(FindCommentsArgsRt)(data); + try { + const queryParams = decodeWithExcessOrThrow(FindCommentsQueryParamsRt)(findQueryParams); - validateFindCommentsPagination(queryParams); + validateFindCommentsPagination(queryParams); - try { const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } = await authorization.getAuthorizationFilter(Operations.findComments); diff --git a/x-pack/plugins/cases/server/client/attachments/types.ts b/x-pack/plugins/cases/server/client/attachments/types.ts index 5260bb7d37b845..75099cdd9f6457 100644 --- a/x-pack/plugins/cases/server/client/attachments/types.ts +++ b/x-pack/plugins/cases/server/client/attachments/types.ts @@ -80,7 +80,7 @@ export interface FindCommentsArgs { /** * Optional parameters for filtering the returned attachments */ - queryParams?: FindCommentsQueryParams; + findQueryParams?: FindCommentsQueryParams; } /** diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts index 213d4a86ea242a..670e6fccfe66b7 100644 --- a/x-pack/plugins/cases/server/client/attachments/update.ts +++ b/x-pack/plugins/cases/server/client/attachments/update.ts @@ -11,6 +11,7 @@ import { CaseCommentModel } from '../../common/models'; import { createCaseError } from '../../common/error'; import { isCommentRequestTypeExternalReference } from '../../../common/utils/attachments'; import type { Case } from '../../../common/api'; +import { CommentPatchRequestRt, decodeWithExcessOrThrow } from '../../../common/api'; import { CASE_SAVED_OBJECT } from '../../../common/constants'; import type { CasesClientArgs } from '..'; import { decodeCommentRequest } from '../utils'; @@ -38,7 +39,7 @@ export async function update( id: queryCommentId, version: queryCommentVersion, ...queryRestAttributes - } = queryParams; + } = decodeWithExcessOrThrow(CommentPatchRequestRt)(queryParams); decodeCommentRequest(queryRestAttributes, externalReferenceAttachmentTypeRegistry); diff --git a/x-pack/plugins/cases/server/client/attachments/validators.test.tsx b/x-pack/plugins/cases/server/client/attachments/validators.test.ts similarity index 100% rename from x-pack/plugins/cases/server/client/attachments/validators.test.tsx rename to x-pack/plugins/cases/server/client/attachments/validators.test.ts diff --git a/x-pack/plugins/cases/server/client/cases/create.test.ts b/x-pack/plugins/cases/server/client/cases/create.test.ts index 648f2bb4c2b055..6bca80528a47ab 100644 --- a/x-pack/plugins/cases/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -90,7 +90,9 @@ describe('create', () => { await expect( // @ts-expect-error foo is an invalid field create({ ...theCase, foo: 'bar' }, clientArgs) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid keys \\"foo\\""`); + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to create case: Error: invalid keys \\"foo\\""` + ); }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 5320b79e6547b6..040e5720cbbbb9 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -40,19 +40,19 @@ export const create = async (data: CasePostRequest, clientArgs: CasesClientArgs) authorization: auth, } = clientArgs; - const query = decodeWithExcessOrThrow(CasePostRequestRt)(data); + try { + const query = decodeWithExcessOrThrow(CasePostRequestRt)(data); - if (query.title.length > MAX_TITLE_LENGTH) { - throw Boom.badRequest( - `The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.` - ); - } + if (query.title.length > MAX_TITLE_LENGTH) { + throw Boom.badRequest( + `The length of the title is too long. The maximum length is ${MAX_TITLE_LENGTH}.` + ); + } - if (query.tags.some(isInvalidTag)) { - throw Boom.badRequest('A tag must contain at least one non-space character'); - } + if (query.tags.some(isInvalidTag)) { + throw Boom.badRequest('A tag must contain at least one non-space character'); + } - try { const savedObjectID = SavedObjectsUtils.generateId(); await auth.ensureAuthorized({ diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index a9479e2ab171a3..5fbefa1b4405b4 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -33,6 +33,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P authorization, fileService, } = clientArgs; + try { const cases = await caseService.getCases({ caseIds: ids }); const entities = new Map(); diff --git a/x-pack/plugins/cases/server/client/cases/update.test.ts b/x-pack/plugins/cases/server/client/cases/update.test.ts index db463b6f22e2b2..e51ccab4ca9b92 100644 --- a/x-pack/plugins/cases/server/client/cases/update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/update.test.ts @@ -266,7 +266,9 @@ describe('update', () => { }, clientArgs ) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid keys \\"foo\\""`); + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: invalid keys \\"foo\\""` + ); }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index b3c725cfcae3d8..18564787dedaca 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -314,9 +314,9 @@ export const update = async ( authorization, } = clientArgs; - const query = decodeWithExcessOrThrow(CasesPatchRequestRt)(cases); - try { + const query = decodeWithExcessOrThrow(CasesPatchRequestRt)(cases); + const myCases = await caseService.getCases({ caseIds: query.cases.map((q) => q.id), }); diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts index df33ee5c595e61..222407fedecf5c 100644 --- a/x-pack/plugins/cases/server/client/configure/client.ts +++ b/x-pack/plugins/cases/server/client/configure/client.ts @@ -30,6 +30,7 @@ import { ConfigurationRt, FindActionConnectorResponseRt, decodeWithExcessOrThrow, + ConfigurationRequestRt, } from '../../../common/api'; import { MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; import { createCaseError } from '../../common/error'; @@ -138,6 +139,7 @@ export async function get( logger, authorization, } = clientArgs; + try { const queryParams = decodeWithExcessOrThrow(GetConfigurationFindRequestRt)(params); @@ -339,7 +341,7 @@ export async function update( } async function create( - configuration: ConfigurationRequest, + configRequest: ConfigurationRequest, clientArgs: CasesClientArgs, casesClientInternal: CasesClientInternal ): Promise { @@ -350,7 +352,11 @@ async function create( user, authorization, } = clientArgs; + try { + const validatedConfigurationRequest = + decodeWithExcessOrThrow(ConfigurationRequestRt)(configRequest); + let error = null; const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } = @@ -364,7 +370,7 @@ async function create( ); const filter = combineAuthorizedAndOwnerFilter( - configuration.owner, + validatedConfigurationRequest.owner, authorizationFilter, Operations.createConfiguration.savedObjectType ); @@ -399,7 +405,7 @@ async function create( await authorization.ensureAuthorized({ operation: Operations.createConfiguration, - entities: [{ owner: configuration.owner, id: savedObjectID }], + entities: [{ owner: validatedConfigurationRequest.owner, id: savedObjectID }], }); const creationDate = new Date().toISOString(); @@ -408,22 +414,22 @@ async function create( try { mappings = ( await casesClientInternal.configuration.createMappings({ - connector: configuration.connector, - owner: configuration.owner, + connector: validatedConfigurationRequest.connector, + owner: validatedConfigurationRequest.owner, refresh: false, }) ).mappings; } catch (e) { error = e.isBoom ? e.output.payload.message - : `Error creating mapping for ${configuration.connector.name}`; + : `Error creating mapping for ${validatedConfigurationRequest.connector.name}`; } const post = await caseConfigureService.post({ unsecuredSavedObjectsClient, attributes: { - ...configuration, - connector: configuration.connector, + ...validatedConfigurationRequest, + connector: validatedConfigurationRequest.connector, created_at: creationDate, created_by: user, updated_at: null, diff --git a/x-pack/plugins/cases/server/client/metrics/client.ts b/x-pack/plugins/cases/server/client/metrics/client.ts index bbf85cc2a01840..6744bb99c9c52c 100644 --- a/x-pack/plugins/cases/server/client/metrics/client.ts +++ b/x-pack/plugins/cases/server/client/metrics/client.ts @@ -10,7 +10,6 @@ import type { CasesMetricsRequest, CasesStatusRequest, CasesStatusResponse, - SingleCaseMetricsRequest, CasesMetricsResponse, } from '../../../common/api'; import type { CasesClient } from '../client'; @@ -19,12 +18,13 @@ import type { CasesClientArgs } from '../types'; import { getStatusTotalsByType } from './get_status_totals'; import { getCaseMetrics } from './get_case_metrics'; import { getCasesMetrics } from './get_cases_metrics'; +import type { GetCaseMetricsParams } from './types'; /** * API for interacting with the metrics. */ export interface MetricsSubClient { - getCaseMetrics(params: SingleCaseMetricsRequest): Promise; + getCaseMetrics(params: GetCaseMetricsParams): Promise; getCasesMetrics(params: CasesMetricsRequest): Promise; /** * Retrieves the total number of open, closed, and in-progress cases. @@ -42,7 +42,7 @@ export const createMetricsSubClient = ( casesClient: CasesClient ): MetricsSubClient => { const casesSubClient: MetricsSubClient = { - getCaseMetrics: (params: SingleCaseMetricsRequest) => + getCaseMetrics: (params: GetCaseMetricsParams) => getCaseMetrics(params, casesClient, clientArgs), getCasesMetrics: (params: CasesMetricsRequest) => getCasesMetrics(params, casesClient, clientArgs), diff --git a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts index 0337c076e13c75..bde457ce4e8af0 100644 --- a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts +++ b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts @@ -6,25 +6,36 @@ */ import { merge } from 'lodash'; -import type { SingleCaseMetricsRequest, SingleCaseMetricsResponse } from '../../../common/api'; -import { SingleCaseMetricsResponseRt } from '../../../common/api'; +import type { SingleCaseMetricsResponse } from '../../../common/api'; +import { + SingleCaseMetricsResponseRt, + SingleCaseMetricsRequestRt, + decodeWithExcessOrThrow, +} from '../../../common/api'; import { Operations } from '../../authorization'; import { createCaseError } from '../../common/error'; import type { CasesClient } from '../client'; import type { CasesClientArgs } from '../types'; +import type { GetCaseMetricsParams } from './types'; import { buildHandlers } from './utils'; import { decodeOrThrow } from '../../../common/api/runtime_types'; export const getCaseMetrics = async ( - params: SingleCaseMetricsRequest, + { caseId, features }: GetCaseMetricsParams, casesClient: CasesClient, clientArgs: CasesClientArgs ): Promise => { const { logger } = clientArgs; try { - await checkAuthorization(params, clientArgs); - const handlers = buildHandlers(params, casesClient, clientArgs); + const queryParams = decodeWithExcessOrThrow(SingleCaseMetricsRequestRt)({ features }); + + await checkAuthorization(caseId, clientArgs); + const handlers = buildHandlers( + { caseId, features: queryParams.features }, + casesClient, + clientArgs + ); const computedMetrics = await Promise.all( Array.from(handlers).map(async (handler) => { @@ -40,23 +51,20 @@ export const getCaseMetrics = async ( } catch (error) { throw createCaseError({ logger, - message: `Failed to retrieve metrics within client for case id: ${params.caseId}: ${error}`, + message: `Failed to retrieve metrics within client for case id: ${caseId}: ${error}`, error, }); } }; -const checkAuthorization = async ( - params: SingleCaseMetricsRequest, - clientArgs: CasesClientArgs -) => { +const checkAuthorization = async (caseId: string, clientArgs: CasesClientArgs) => { const { services: { caseService }, authorization, } = clientArgs; const caseInfo = await caseService.getCase({ - id: params.caseId, + id: caseId, }); await authorization.ensureAuthorized({ diff --git a/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts index 68986d8294cdf7..0222c692b06371 100644 --- a/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts +++ b/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts @@ -26,9 +26,9 @@ export const getCasesMetrics = async ( ): Promise => { const { logger } = clientArgs; - const queryParams = decodeWithExcessOrThrow(CasesMetricsRequestRt)(params); - try { + const queryParams = decodeWithExcessOrThrow(CasesMetricsRequestRt)(params); + const handlers = buildHandlers(queryParams, casesClient, clientArgs); const computedMetrics = await Promise.all( diff --git a/x-pack/plugins/cases/server/client/metrics/types.ts b/x-pack/plugins/cases/server/client/metrics/types.ts index 829d7369195580..a28b3294cccfbf 100644 --- a/x-pack/plugins/cases/server/client/metrics/types.ts +++ b/x-pack/plugins/cases/server/client/metrics/types.ts @@ -37,3 +37,8 @@ export interface AllCasesBaseHandlerCommonOptions extends BaseHandlerCommonOptio to?: string; owner?: string | string[]; } + +export interface GetCaseMetricsParams { + caseId: string; + features: string[]; +} diff --git a/x-pack/plugins/cases/server/client/metrics/utils.ts b/x-pack/plugins/cases/server/client/metrics/utils.ts index 90d7b406584569..8fb84c24812976 100644 --- a/x-pack/plugins/cases/server/client/metrics/utils.ts +++ b/x-pack/plugins/cases/server/client/metrics/utils.ts @@ -14,15 +14,15 @@ import { AlertDetails } from './alerts/details'; import { Actions } from './actions'; import { Connectors } from './connectors'; import { Lifespan } from './lifespan'; -import type { MetricsHandler } from './types'; +import type { GetCaseMetricsParams, MetricsHandler } from './types'; import { MTTR } from './all_cases/mttr'; const isSingleCaseMetrics = ( - params: SingleCaseMetricsRequest | CasesMetricsRequest -): params is SingleCaseMetricsRequest => (params as SingleCaseMetricsRequest).caseId != null; + params: GetCaseMetricsParams | CasesMetricsRequest +): params is GetCaseMetricsParams => (params as GetCaseMetricsParams).caseId != null; export const buildHandlers = ( - params: SingleCaseMetricsRequest | CasesMetricsRequest, + params: GetCaseMetricsParams | CasesMetricsRequest, casesClient: CasesClient, clientArgs: CasesClientArgs ): Set> => { diff --git a/x-pack/plugins/cases/server/common/types/case.test.ts b/x-pack/plugins/cases/server/common/types/case.test.ts index 2216b9515425bc..e5135d7bd1cd6a 100644 --- a/x-pack/plugins/cases/server/common/types/case.test.ts +++ b/x-pack/plugins/cases/server/common/types/case.test.ts @@ -8,64 +8,93 @@ import { omit } from 'lodash'; import { CaseStatuses } from '@kbn/cases-components'; import { ConnectorTypes, CaseSeverity, SECURITY_SOLUTION_OWNER } from '../../../common'; -import { CaseTransformedAttributesRt, getPartialCaseTransformedAttributesRt } from './case'; +import { + CaseTransformedAttributesRt, + getPartialCaseTransformedAttributesRt, + OwnerRt, +} from './case'; +import { decodeOrThrow } from '../../../common/api'; -describe('getPartialCaseTransformedAttributesRt', () => { - const theCaseAttributes = { - closed_at: null, - closed_by: null, - connector: { - id: 'none', - name: 'none', - type: ConnectorTypes.none, - fields: null, - }, - created_at: '2019-11-25T21:54:48.952Z', - created_by: { - full_name: 'elastic', - email: 'testemail@elastic.co', - username: 'elastic', - }, - severity: CaseSeverity.LOW, - duration: null, - description: 'This is a brand new case of a bad meanie defacing data', - external_service: null, - title: 'Super Bad Security Issue', - status: CaseStatuses.open, - tags: ['defacement'], - updated_at: '2019-11-25T21:54:48.952Z', - updated_by: { - full_name: 'elastic', - email: 'testemail@elastic.co', - username: 'elastic', - }, - settings: { - syncAlerts: true, - }, - owner: SECURITY_SOLUTION_OWNER, - assignees: [], - }; - const caseTransformedAttributesProps = CaseTransformedAttributesRt.types.reduce( - (acc, type) => ({ ...acc, ...type.type.props }), - {} - ); +describe('case types', () => { + describe('getPartialCaseTransformedAttributesRt', () => { + const theCaseAttributes = { + closed_at: null, + closed_by: null, + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + created_at: '2019-11-25T21:54:48.952Z', + created_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + severity: CaseSeverity.LOW, + duration: null, + description: 'This is a brand new case of a bad meanie defacing data', + external_service: null, + title: 'Super Bad Security Issue', + status: CaseStatuses.open, + tags: ['defacement'], + updated_at: '2019-11-25T21:54:48.952Z', + updated_by: { + full_name: 'elastic', + email: 'testemail@elastic.co', + username: 'elastic', + }, + settings: { + syncAlerts: true, + }, + owner: SECURITY_SOLUTION_OWNER, + assignees: [], + }; + const caseTransformedAttributesProps = CaseTransformedAttributesRt.types.reduce( + (acc, type) => ({ ...acc, ...type.type.props }), + {} + ); - const type = getPartialCaseTransformedAttributesRt(); + const type = getPartialCaseTransformedAttributesRt(); - it.each(Object.keys(caseTransformedAttributesProps))('does not throw if %s is omitted', (key) => { - const theCase = omit(theCaseAttributes, key); - const decodedRes = type.decode(theCase); + it.each(Object.keys(caseTransformedAttributesProps))( + 'does not throw if %s is omitted', + (key) => { + const theCase = omit(theCaseAttributes, key); + const decodedRes = type.decode(theCase); - expect(decodedRes._tag).toEqual('Right'); - // @ts-expect-error: the check above ensures that right exists - expect(decodedRes.right).toEqual(theCase); + expect(decodedRes._tag).toEqual('Right'); + // @ts-expect-error: the check above ensures that right exists + expect(decodedRes.right).toEqual(theCase); + } + ); + + it('removes excess properties', () => { + const decodedRes = type.decode({ description: 'test', 'not-exists': 'excess' }); + + expect(decodedRes._tag).toEqual('Right'); + // @ts-expect-error: the check above ensures that right exists + expect(decodedRes.right).toEqual({ description: 'test' }); + }); }); - it('removes excess properties', () => { - const decodedRes = type.decode({ description: 'test', 'not-exists': 'excess' }); + describe('OwnerRt', () => { + it('strips excess fields from the result', () => { + const res = decodeOrThrow(OwnerRt)({ + owner: 'yes', + created_at: '123', + }); + + expect(res).toStrictEqual({ + owner: 'yes', + }); + }); - expect(decodedRes._tag).toEqual('Right'); - // @ts-expect-error: the check above ensures that right exists - expect(decodedRes.right).toEqual({ description: 'test' }); + it('throws an error when owner is not present', () => { + expect(() => decodeOrThrow(OwnerRt)({})).toThrowErrorMatchingInlineSnapshot( + `"Invalid value \\"undefined\\" supplied to \\"owner\\""` + ); + }); }); }); diff --git a/x-pack/plugins/cases/server/common/types/case.ts b/x-pack/plugins/cases/server/common/types/case.ts index 7de3b51576fd40..fb1f3bf0e971e7 100644 --- a/x-pack/plugins/cases/server/common/types/case.ts +++ b/x-pack/plugins/cases/server/common/types/case.ts @@ -7,7 +7,7 @@ import type { SavedObject } from '@kbn/core-saved-objects-server'; import type { Type } from 'io-ts'; -import { exact, partial } from 'io-ts'; +import { exact, partial, strict, string } from 'io-ts'; import type { CaseAttributes } from '../../../common/api'; import { CaseAttributesRt } from '../../../common/api'; import type { ConnectorPersisted } from './connectors'; @@ -64,3 +64,5 @@ export const getPartialCaseTransformedAttributesRt = (): Type; export type CaseSavedObjectTransformed = SavedObject; + +export const OwnerRt = strict({ owner: string }); diff --git a/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts b/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts index d732b0af71e374..005d1b76f54fc6 100644 --- a/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts +++ b/x-pack/plugins/cases/server/routes/api/comments/find_comments.ts @@ -29,7 +29,7 @@ export const findCommentsRoute = createCasesRoute({ return response.ok({ body: await client.attachments.find({ caseID: request.params.case_id, - queryParams: query, + findQueryParams: query, }), }); } catch (error) { diff --git a/x-pack/plugins/cases/server/routes/api/internal/bulk_delete_file_attachments.ts b/x-pack/plugins/cases/server/routes/api/internal/bulk_delete_file_attachments.ts index a5c68441599684..09b84ee604afc0 100644 --- a/x-pack/plugins/cases/server/routes/api/internal/bulk_delete_file_attachments.ts +++ b/x-pack/plugins/cases/server/routes/api/internal/bulk_delete_file_attachments.ts @@ -11,7 +11,10 @@ import { INTERNAL_DELETE_FILE_ATTACHMENTS_URL } from '../../../../common/constan import { createCasesRoute } from '../create_cases_route'; import { createCaseError } from '../../../common/error'; import { escapeHatch } from '../utils'; -import type { BulkDeleteFileAttachmentsRequest } from '../../../../common/api'; +import { + BulkDeleteFileAttachmentsRequestRt, + decodeWithExcessOrThrow, +} from '../../../../common/api'; export const bulkDeleteFileAttachments = createCasesRoute({ method: 'post', @@ -27,7 +30,7 @@ export const bulkDeleteFileAttachments = createCasesRoute({ const caseContext = await context.cases; const client = await caseContext.getCasesClient(); - const requestBody = request.body as BulkDeleteFileAttachmentsRequest; + const requestBody = decodeWithExcessOrThrow(BulkDeleteFileAttachmentsRequestRt)(request.body); await client.attachments.bulkDeleteFileAttachments({ caseId: request.params.case_id, diff --git a/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts b/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts index 8f72a42b91b412..3465d259607626 100644 --- a/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts +++ b/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import type { BulkGetAttachmentsRequest } from '../../../../common/api'; +import { BulkGetAttachmentsRequestRt, decodeWithExcessOrThrow } from '../../../../common/api'; import { INTERNAL_BULK_GET_ATTACHMENTS_URL } from '../../../../common/constants'; import { createCaseError } from '../../../common/error'; @@ -26,12 +26,13 @@ export const bulkGetAttachmentsRoute = createCasesRoute({ try { const caseContext = await context.cases; const client = await caseContext.getCasesClient(); - const body = request.body as BulkGetAttachmentsRequest; + + const requestBody = decodeWithExcessOrThrow(BulkGetAttachmentsRequestRt)(request.body); return response.ok({ body: await client.attachments.bulkGet({ caseID: request.params.case_id, - attachmentIDs: body.ids, + attachmentIDs: requestBody.ids, }), }); } catch (error) { diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts index 5679c532502d74..47649adf75a8a5 100644 --- a/x-pack/plugins/cases/server/services/cases/index.test.ts +++ b/x-pack/plugins/cases/server/services/cases/index.test.ts @@ -13,10 +13,10 @@ * connector.id. */ -import { omit } from 'lodash'; +import { omit, unset } from 'lodash'; import type { CaseAttributes, CaseConnector, CaseFullExternalService } from '../../../common/api'; import { CaseSeverity, CaseStatuses } from '../../../common/api'; -import { CASE_SAVED_OBJECT } from '../../../common/constants'; +import { CASE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { SavedObject, @@ -1882,6 +1882,48 @@ describe('CasesService', () => { 'external_service' ); + describe('getCaseIdsByAlertId', () => { + it('strips excess fields', async () => { + const findMockReturn = createSOFindResponse([createFindSO({ caseId: '2' })]); + unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn); + + const res = await service.getCaseIdsByAlertId({ alertId: '1' }); + expect(res).toStrictEqual({ + saved_objects: [ + { + id: '2', + score: 0, + references: [], + type: CASE_SAVED_OBJECT, + attributes: { owner: SECURITY_SOLUTION_OWNER }, + }, + ], + total: 1, + per_page: 1, + page: 1, + }); + }); + + it('throws an error when the owner field is not present', async () => { + const findMockReturn = createSOFindResponse([createFindSO({ caseId: '2' })]); + unset(findMockReturn, 'saved_objects[0].attributes.owner'); + unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn); + + await expect( + service.getCaseIdsByAlertId({ alertId: '1' }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid value \\"undefined\\" supplied to \\"owner\\""` + ); + }); + + it('does not throw an error when the owner field exists', async () => { + const findMockReturn = createSOFindResponse([createFindSO({ caseId: '2' })]); + unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn); + + await expect(service.getCaseIdsByAlertId({ alertId: '1' })).resolves.not.toThrow(); + }); + }); + describe('getCase', () => { it('decodes correctly', async () => { unsecuredSavedObjectsClient.get.mockResolvedValue(createCaseSavedObjectResponse()); @@ -1902,13 +1944,67 @@ describe('CasesService', () => { } ); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { - const theCase = createCaseSavedObjectResponse(); + it('strips out excess attributes', async () => { + const theCase = createCaseSavedObjectResponse({ + connector: createESJiraConnector(), + externalService: null, + }); const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' }; unsecuredSavedObjectsClient.get.mockResolvedValue({ ...theCase, attributes }); - await expect(service.getCase({ id: 'a' })).resolves.toEqual({ attributes }); + await expect(service.getCase({ id: 'a' })).resolves.toMatchInlineSnapshot(` + Object { + "attributes": Object { + "assignees": Array [], + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": Object { + "issueType": "bug", + "parent": "2", + "priority": "high", + }, + "id": "1", + "name": ".jira", + "type": ".jira", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": null, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "connectorId", + "type": "action", + }, + ], + "type": "cases", + } + `); }); }); @@ -1938,8 +2034,7 @@ describe('CasesService', () => { } ); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const theCase = createCaseSavedObjectResponse(); const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' }; unsecuredSavedObjectsClient.resolve.mockResolvedValue({ @@ -1947,7 +2042,64 @@ describe('CasesService', () => { outcome: 'exactMatch', }); - await expect(service.getResolveCase({ id: 'a' })).resolves.toEqual({ attributes }); + await expect(service.getResolveCase({ id: 'a' })).resolves.toMatchInlineSnapshot(` + Object { + "outcome": "exactMatch", + "saved_object": Object { + "attributes": Object { + "assignees": Array [], + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + } + `); }); }); @@ -2065,15 +2217,72 @@ describe('CasesService', () => { } ); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const theCase = createCaseSavedObjectResponse(); const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' }; unsecuredSavedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [{ ...theCase, attributes }], }); - await expect(service.getCases({ caseIds: ['a', 'b'] })).resolves.toEqual({ attributes }); + await expect(service.getCases({ caseIds: ['a', 'b'] })).resolves.toMatchInlineSnapshot(` + Object { + "saved_objects": Array [ + Object { + "attributes": Object { + "assignees": Array [], + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + }, + ], + } + `); }); }); @@ -2107,8 +2316,7 @@ describe('CasesService', () => { } ); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const theCase = createCaseSavedObjectResponse(); const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' }; const findMockReturn = createSOFindResponse([ @@ -2118,7 +2326,123 @@ describe('CasesService', () => { unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn); - await expect(service.findCases()).resolves.toEqual({ attributes }); + await expect(service.findCases()).resolves.toMatchInlineSnapshot(` + Object { + "page": 1, + "per_page": 2, + "saved_objects": Array [ + Object { + "attributes": Object { + "assignees": Array [], + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "score": 0, + "type": "cases", + }, + Object { + "attributes": Object { + "assignees": Array [], + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "score": 0, + "type": "cases", + }, + ], + "total": 2, + } + `); }); }); @@ -2150,8 +2474,7 @@ describe('CasesService', () => { } ); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const theCase = createCaseSavedObjectResponse(); const attributes = { ...theCase.attributes, 'not-exists': 'not-exists' }; unsecuredSavedObjectsClient.create.mockResolvedValue({ ...theCase, attributes }); @@ -2161,7 +2484,61 @@ describe('CasesService', () => { attributes: createCasePostParams({ connector: createJiraConnector() }), id: '1', }) - ).resolves.toEqual({ attributes }); + ).resolves.toMatchInlineSnapshot(` + Object { + "attributes": Object { + "assignees": Array [], + "closed_at": null, + "closed_by": null, + "connector": Object { + "fields": null, + "id": "none", + "name": "none", + "type": ".none", + }, + "created_at": "2019-11-25T21:54:48.952Z", + "created_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + "description": "This is a brand new case of a bad meanie defacing data", + "duration": null, + "external_service": Object { + "connector_id": "none", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "owner": "securitySolution", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "defacement", + ], + "title": "Super Bad Security Issue", + "updated_at": "2019-11-25T21:54:48.952Z", + "updated_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + "id": "1", + "references": Array [], + "type": "cases", + } + `); }); }); diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 9236a5c68554da..0c1f91f53a075c 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -53,6 +53,7 @@ import { CaseTransformedAttributesRt, CasePersistedStatus, getPartialCaseTransformedAttributesRt, + OwnerRt, } from '../../common/types/case'; import type { GetCaseIdsByAlertIdArgs, @@ -134,7 +135,15 @@ export class CasesService { aggs: this.buildCaseIdsAggs(MAX_DOCS_PER_PAGE), filter: combinedFilter, }); - return response; + + const owners: Array> = []; + for (const so of response.saved_objects) { + const validatedAttributes = decodeOrThrow(OwnerRt)(so.attributes); + + owners.push(Object.assign(so, { attributes: validatedAttributes })); + } + + return Object.assign(response, { saved_objects: owners }); } catch (error) { this.log.error(`Error on GET all cases for alert id ${alertId}: ${error}`); throw error; @@ -199,7 +208,7 @@ export class CasesService { [status in CaseStatuses]: number; }> { const cases = await this.unsecuredSavedObjectsClient.find< - CasePersistedAttributes, + unknown, { statuses: { buckets: Array<{ @@ -459,7 +468,7 @@ export class CasesService { this.log.debug(`Attempting to GET all reporters`); const results = await this.unsecuredSavedObjectsClient.find< - CasePersistedAttributes, + unknown, { reporters: { buckets: Array<{ @@ -521,7 +530,7 @@ export class CasesService { this.log.debug(`Attempting to GET all cases`); const results = await this.unsecuredSavedObjectsClient.find< - CasePersistedAttributes, + unknown, { tags: { buckets: Array<{ key: string }> } } >({ type: CASE_SAVED_OBJECT, diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts index e9b1d155f52345..0f991a135c5cee 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { set, omit } from 'lodash'; +import { set, omit, unset } from 'lodash'; import { loggerMock } from '@kbn/logging-mocks'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { @@ -1599,6 +1599,60 @@ describe('CaseUserActionService', () => { const pushes = [{ date: new Date(), connectorId: '123' }]; + describe('getAll', () => { + it('does not throw when the required fields are present', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValue( + createSOFindResponse([{ ...createUserActionSO(), score: 0 }]) + ); + + await expect(service.getAll('1')).resolves.not.toThrow(); + }); + + it('throws when payload does not exist', async () => { + const findMockReturn = createSOFindResponse([{ ...createUserActionSO(), score: 0 }]); + unset(findMockReturn, 'saved_objects[0].attributes.payload'); + + unsecuredSavedObjectsClient.find.mockResolvedValue(findMockReturn); + + await expect(service.getAll('1')).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid value \\"undefined\\" supplied to \\"payload\\""` + ); + }); + + it('strips excess fields', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValue( + createSOFindResponse([ + { + ...createUserActionSO({ + attributesOverrides: { + // @ts-expect-error foo is not a valid field for attributesOverrides + foo: 'bar', + }, + }), + score: 0, + }, + ]) + ); + + const res = await service.getAll('1'); + expect(res).toStrictEqual( + createSOFindResponse([ + { + ...createUserActionSO({ + attributesOverrides: { + // @ts-expect-error these fields are populated by the legacy transformation logic but aren't valid for the override type + action_id: '100', + case_id: '1', + comment_id: null, + }, + }), + score: 0, + }, + ]) + ); + }); + }); + describe('getConnectorFieldsBeforeLatestPush', () => { const getAggregations = ( userAction: SavedObject @@ -1677,7 +1731,7 @@ describe('CaseUserActionService', () => { ); }); - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const userAction = createUserActionSO(); const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' }; const userActionWithExtraAttributes = { ...userAction, attributes, score: 0 }; @@ -1687,9 +1741,38 @@ describe('CaseUserActionService', () => { unsecuredSavedObjectsClient.find.mockResolvedValue({ ...soFindRes, aggregations }); soSerializerMock.rawToSavedObject.mockReturnValue(userActionWithExtraAttributes); - await expect(service.getConnectorFieldsBeforeLatestPush('1', pushes)).resolves.toEqual({ - attributes: userAction.attributes, - }); + await expect(service.getConnectorFieldsBeforeLatestPush('1', pushes)).resolves + .toMatchInlineSnapshot(` + Map { + "servicenow" => Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + } + `); }); }); @@ -1738,16 +1821,41 @@ describe('CaseUserActionService', () => { ); }); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const userAction = createUserActionSO(); const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' }; const soFindRes = createSOFindResponse([{ ...userAction, attributes, score: 0 }]); unsecuredSavedObjectsClient.find.mockResolvedValue(soFindRes); - await expect(service.getMostRecentUserAction('123')).resolves.toEqual({ - attributes: userAction.attributes, - }); + await expect(service.getMostRecentUserAction('123')).resolves.toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + } + `); }); }); @@ -1840,7 +1948,7 @@ describe('CaseUserActionService', () => { ); }); - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const userAction = createUserActionSO(); const pushUserAction = pushConnectorUserAction(); const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' }; @@ -1851,9 +1959,97 @@ describe('CaseUserActionService', () => { unsecuredSavedObjectsClient.find.mockResolvedValue({ ...soFindRes, aggregations }); soSerializerMock.rawToSavedObject.mockReturnValue(userActionWithExtraAttributes); - await expect(service.getCaseConnectorInformation('1')).resolves.toEqual({ - attributes: userAction.attributes, - }); + await expect(service.getCaseConnectorInformation('1')).resolves + .toMatchInlineSnapshot(` + Array [ + Object { + "connectorId": undefined, + "fields": Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + "push": Object { + "mostRecent": Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + "oldest": Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + }, + }, + ] + `); }); }); @@ -1945,21 +2141,143 @@ describe('CaseUserActionService', () => { ); }); - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const userAction = createUserActionSO(); const pushUserAction = pushConnectorUserAction(); const attributes = { ...pushUserAction.attributes, 'not-exists': 'not-exists' }; - const pushActionWithExtraAttributes = { ...userAction, attributes, score: 0 }; + const pushActionWithExtraAttributes = { ...pushUserAction, attributes, score: 0 }; const aggregations = getAggregations(userAction, pushActionWithExtraAttributes); const soFindRes = createSOFindResponse([{ ...userAction, score: 0 }]); unsecuredSavedObjectsClient.find.mockResolvedValue({ ...soFindRes, aggregations }); soSerializerMock.rawToSavedObject.mockReturnValueOnce(userAction); soSerializerMock.rawToSavedObject.mockReturnValueOnce(pushActionWithExtraAttributes); + soSerializerMock.rawToSavedObject.mockReturnValueOnce(pushActionWithExtraAttributes); - await expect(service.getCaseConnectorInformation('1')).resolves.toEqual({ - attributes: userAction.attributes, - }); + await expect(service.getCaseConnectorInformation('1')).resolves + .toMatchInlineSnapshot(` + Array [ + Object { + "connectorId": undefined, + "fields": Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "type": "cases-user-actions", + }, + "push": Object { + "mostRecent": Object { + "attributes": Object { + "action": "push_to_service", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "externalService": Object { + "connector_id": "100", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + }, + "type": "pushed", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "100", + "name": "pushConnectorId", + "type": "action", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + "oldest": Object { + "attributes": Object { + "action": "push_to_service", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "externalService": Object { + "connector_id": "100", + "connector_name": ".jira", + "external_id": "100", + "external_title": "awesome", + "external_url": "http://www.google.com", + "pushed_at": "2019-11-25T21:54:48.952Z", + "pushed_by": Object { + "email": "testemail@elastic.co", + "full_name": "elastic", + "username": "elastic", + }, + }, + }, + "type": "pushed", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "100", + "name": "pushConnectorId", + "type": "action", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + }, + }, + ] + `); }); }); }); diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts index 15e797df5374f4..950e8efcf8e8a7 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.ts @@ -5,12 +5,20 @@ * 2.0. */ -import type { SavedObjectsFindResponse, SavedObjectsRawDoc } from '@kbn/core/server'; +import type { + SavedObjectsFindResponse, + SavedObjectsFindResult, + SavedObjectsRawDoc, +} from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { KueryNode } from '@kbn/es-query'; import type { CaseUserActionDeprecatedResponse } from '../../../common/api'; -import { decodeOrThrow, ActionTypes } from '../../../common/api'; +import { + decodeOrThrow, + ActionTypes, + CaseUserActionDeprecatedResponseRt, +} from '../../../common/api'; import { CASE_SAVED_OBJECT, CASE_USER_ACTION_SAVED_OBJECT, @@ -515,10 +523,22 @@ export class CaseUserActionService { sortOrder: 'asc', }); - return legacyTransformFindResponseToExternalModel( + const transformedUserActions = legacyTransformFindResponseToExternalModel( userActions, this.context.persistableStateAttachmentTypeRegistry ); + + const validatedUserActions: Array> = + []; + for (const so of transformedUserActions.saved_objects) { + const validatedAttributes = decodeOrThrow(CaseUserActionDeprecatedResponseRt)( + so.attributes + ); + + validatedUserActions.push(Object.assign(so, { attributes: validatedAttributes })); + } + + return Object.assign(transformedUserActions, { saved_objects: validatedUserActions }); } catch (error) { this.context.log.error(`Error on GET case user action case id: ${caseId}: ${error}`); throw error; @@ -636,7 +656,7 @@ export class CaseUserActionService { public async getCaseUserActionStats({ caseId }: { caseId: string }) { const response = await this.context.unsecuredSavedObjectsClient.find< - UserActionPersistedAttributes, + unknown, UserActionsStatsAggsResult >({ type: CASE_USER_ACTION_SAVED_OBJECT, @@ -680,7 +700,7 @@ export class CaseUserActionService { public async getUsers({ caseId }: { caseId: string }): Promise { const response = await this.context.unsecuredSavedObjectsClient.find< - UserActionPersistedAttributes, + unknown, ParticipantsAggsResult >({ type: CASE_USER_ACTION_SAVED_OBJECT, diff --git a/x-pack/plugins/cases/server/services/user_actions/operations/__snapshots__/find.test.ts.snap b/x-pack/plugins/cases/server/services/user_actions/operations/__snapshots__/find.test.ts.snap new file mode 100644 index 00000000000000..bc070a00dd4aca --- /dev/null +++ b/x-pack/plugins/cases/server/services/user_actions/operations/__snapshots__/find.test.ts.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UserActionsService: Finder Decoding: find strips out excess attributes 1`] = ` +Object { + "page": 1, + "per_page": 1, + "saved_objects": Array [ + Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, + ], + "total": 1, +} +`; + +exports[`UserActionsService: Finder Decoding: findStatusChanges strips out excess attributes 1`] = ` +Array [ + Object { + "attributes": Object { + "action": "create", + "comment_id": null, + "created_at": "abc", + "created_by": Object { + "email": "a", + "full_name": "abc", + "username": "b", + }, + "owner": "securitySolution", + "payload": Object { + "title": "a new title", + }, + "type": "title", + }, + "id": "100", + "references": Array [ + Object { + "id": "1", + "name": "associated-cases", + "type": "cases", + }, + ], + "score": 0, + "type": "cases-user-actions", + }, +] +`; diff --git a/x-pack/plugins/cases/server/services/user_actions/operations/find.test.ts b/x-pack/plugins/cases/server/services/user_actions/operations/find.test.ts index 3a7c5e4c149390..64c07bae741d12 100644 --- a/x-pack/plugins/cases/server/services/user_actions/operations/find.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/operations/find.test.ts @@ -135,16 +135,13 @@ describe('UserActionsService: Finder', () => { ); }); - // TODO: Unskip when all types are converted to strict - it.skip('strips out excess attributes', async () => { + it('strips out excess attributes', async () => { const userAction = createUserActionSO(); const attributes = { ...userAction.attributes, 'not-exists': 'not-exists' }; const soFindRes = createSOFindResponse([{ ...userAction, attributes, score: 0 }]); method(soFindRes); - await expect(finder[soMethodName]({ caseId: '1' })).resolves.toEqual({ - attributes: userAction.attributes, - }); + await expect(finder[soMethodName]({ caseId: '1' })).resolves.toMatchSnapshot(); }); }); }); diff --git a/x-pack/plugins/cases/server/services/user_profiles/index.ts b/x-pack/plugins/cases/server/services/user_profiles/index.ts index 46400e906d1f63..d01d15f6fce48c 100644 --- a/x-pack/plugins/cases/server/services/user_profiles/index.ts +++ b/x-pack/plugins/cases/server/services/user_profiles/index.ts @@ -70,11 +70,11 @@ export class UserProfileService { } public async suggest(request: KibanaRequest): Promise { - const params = decodeWithExcessOrThrow(SuggestUserProfilesRequestRt)(request.body); + try { + const params = decodeWithExcessOrThrow(SuggestUserProfilesRequestRt)(request.body); - const { name, size, owners } = params; + const { name, size, owners } = params; - try { this.validateInitialization(); const licensingService = new LicensingService( @@ -110,7 +110,7 @@ export class UserProfileService { } catch (error) { throw createCaseError({ logger: this.logger, - message: `Failed to retrieve suggested user profiles in service for name: ${name} owners: [${owners}]: ${error}`, + message: `Failed to retrieve suggested user profiles in service: ${error}`, error, }); } diff --git a/x-pack/plugins/cloud_security_posture/public/application/csp_router.test.tsx b/x-pack/plugins/cloud_security_posture/public/application/csp_router.test.tsx index 36499b4d72baa6..b4f804b5d20f0e 100644 --- a/x-pack/plugins/cloud_security_posture/public/application/csp_router.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/application/csp_router.test.tsx @@ -17,6 +17,9 @@ import { QueryClientProviderProps } from '@tanstack/react-query'; jest.mock('../pages', () => ({ Findings: () =>
Findings
, ComplianceDashboard: () =>
ComplianceDashboard
, + VulnerabilityDashboard: () => ( +
VulnerabilityDashboard
+ ), Rules: () =>
Rules
, Benchmarks: () =>
Benchmarks
, })); @@ -57,6 +60,7 @@ describe('CspRouter', () => { expect(result.queryByTestId('Findings')).toBeInTheDocument(); expect(result.queryByTestId('ComplianceDashboard')).not.toBeInTheDocument(); + expect(result.queryByTestId('VulnerabilityDashboard')).not.toBeInTheDocument(); expect(result.queryByTestId('Benchmarks')).not.toBeInTheDocument(); expect(result.queryByTestId('Rules')).not.toBeInTheDocument(); }); @@ -66,6 +70,18 @@ describe('CspRouter', () => { const result = renderCspRouter(); expect(result.queryByTestId('ComplianceDashboard')).toBeInTheDocument(); + expect(result.queryByTestId('VulnerabilityDashboard')).not.toBeInTheDocument(); + expect(result.queryByTestId('Findings')).not.toBeInTheDocument(); + expect(result.queryByTestId('Benchmarks')).not.toBeInTheDocument(); + expect(result.queryByTestId('Rules')).not.toBeInTheDocument(); + }); + + it('should render the Vulnerability Dashboard', () => { + history.push('/cloud_security_posture/vulnerability_dashboard'); + const result = renderCspRouter(); + + expect(result.queryByTestId('VulnerabilityDashboard')).toBeInTheDocument(); + expect(result.queryByTestId('ComplianceDashboard')).not.toBeInTheDocument(); expect(result.queryByTestId('Findings')).not.toBeInTheDocument(); expect(result.queryByTestId('Benchmarks')).not.toBeInTheDocument(); expect(result.queryByTestId('Rules')).not.toBeInTheDocument(); @@ -78,6 +94,7 @@ describe('CspRouter', () => { expect(result.queryByTestId('Benchmarks')).toBeInTheDocument(); expect(result.queryByTestId('Findings')).not.toBeInTheDocument(); expect(result.queryByTestId('ComplianceDashboard')).not.toBeInTheDocument(); + expect(result.queryByTestId('VulnerabilityDashboard')).not.toBeInTheDocument(); expect(result.queryByTestId('Rules')).not.toBeInTheDocument(); }); @@ -88,6 +105,7 @@ describe('CspRouter', () => { expect(result.queryByTestId('Rules')).toBeInTheDocument(); expect(result.queryByTestId('Findings')).not.toBeInTheDocument(); expect(result.queryByTestId('ComplianceDashboard')).not.toBeInTheDocument(); + expect(result.queryByTestId('VulnerabilityDashboard')).not.toBeInTheDocument(); expect(result.queryByTestId('Benchmarks')).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cloud_security_posture/public/application/csp_router.tsx b/x-pack/plugins/cloud_security_posture/public/application/csp_router.tsx index 094425707ae8e9..75f294215dfb52 100644 --- a/x-pack/plugins/cloud_security_posture/public/application/csp_router.tsx +++ b/x-pack/plugins/cloud_security_posture/public/application/csp_router.tsx @@ -30,6 +30,10 @@ export const CspRouter = ({ securitySolutionContext }: CspRouterProps) => { + diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts index fe29c49594acb7..1e89c88b1c9e34 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/constants.ts @@ -19,6 +19,10 @@ const NAV_ITEMS_NAMES = { DASHBOARD: i18n.translate('xpack.csp.navigation.dashboardNavItemLabel', { defaultMessage: 'Cloud Security Posture', }), + VULNERABILITY_DASHBOARD: i18n.translate( + 'xpack.csp.navigation.vulnerabilityDashboardNavItemLabel', + { defaultMessage: 'Cloud Native Vulnerability Management' } + ), FINDINGS: i18n.translate('xpack.csp.navigation.findingsNavItemLabel', { defaultMessage: 'Findings', }), @@ -39,6 +43,11 @@ export const cloudPosturePages: Record = { path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/dashboard`, id: 'cloud_security_posture-dashboard', }, + vulnerability_dashboard: { + name: NAV_ITEMS_NAMES.VULNERABILITY_DASHBOARD, + path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/vulnerability_dashboard`, + id: 'cloud_security_posture-vulnerability_dashboard', + }, findings: { name: NAV_ITEMS_NAMES.FINDINGS, path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/findings`, diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/security_solution_links.test.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/security_solution_links.test.ts index 072776cde5a0cb..ae542ba885ec94 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/security_solution_links.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/security_solution_links.test.ts @@ -14,7 +14,12 @@ const chance = new Chance(); describe('getSecuritySolutionLink', () => { it('gets the correct link properties', () => { - const cspPage = chance.pickone(['dashboard', 'findings', 'benchmarks']); + const cspPage = chance.pickone([ + 'dashboard', + 'findings', + 'benchmarks', + 'vulnerability_dashboard', + ]); const link = getSecuritySolutionLink(cspPage); @@ -26,7 +31,12 @@ describe('getSecuritySolutionLink', () => { describe('getSecuritySolutionNavTab', () => { it('gets the correct nav tab properties', () => { - const cspPage = chance.pickone(['dashboard', 'findings', 'benchmarks']); + const cspPage = chance.pickone([ + 'dashboard', + 'findings', + 'benchmarks', + 'vulnerability_dashboard', + ]); const basePath = chance.word(); const navTab = getSecuritySolutionNavTab(cspPage, basePath); diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts index 8bc47e489e5524..b7b1723a16066d 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/types.ts @@ -14,7 +14,7 @@ export interface CspPageNavigationItem extends CspNavigationItem { id: CloudSecurityPosturePageId; } -export type CspPage = 'dashboard' | 'findings' | 'benchmarks'; +export type CspPage = 'dashboard' | 'vulnerability_dashboard' | 'findings' | 'benchmarks'; export type CspBenchmarksPage = 'rules'; /** @@ -23,6 +23,7 @@ export type CspBenchmarksPage = 'rules'; */ export type CloudSecurityPosturePageId = | 'cloud_security_posture-dashboard' + | 'cloud_security_posture-vulnerability_dashboard' | 'cloud_security_posture-findings' | 'cloud_security_posture-benchmarks' | 'cloud_security_posture-benchmarks-rules'; diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx index 2000ccef578b8f..3db7affc2d8795 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page_title.tsx @@ -6,14 +6,29 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; -export const CloudPosturePageTitle = ({ title }: { title: string }) => ( +export const CloudPosturePageTitle = ({ title, isBeta }: { title: string; isBeta?: boolean }) => (

{title}

+ {isBeta && ( + + + } + tooltipPosition="bottom" + /> + + )}
); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts index 748f7c46766e2e..97b975229a0416 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts @@ -11,6 +11,7 @@ export const DASHBOARD_SUMMARY_CONTAINER = 'dashboard-summary-section'; export const KUBERNETES_DASHBOARD_CONTAINER = 'kubernetes-dashboard-container'; export const CLOUD_DASHBOARD_CONTAINER = 'cloud-dashboard-container'; export const CLOUD_POSTURE_DASHBOARD_PAGE_HEADER = 'cloud-posture-dashboard-page-header'; +export const VULNERABILITY_DASHBOARD_PAGE_HEADER = 'vulnerability-dashboard-page-header'; export const DASHBOARD_COUNTER_CARDS = { CLUSTERS_EVALUATED: 'dashboard-counter-card-clusters-evaluated', diff --git a/x-pack/plugins/cloud_security_posture/public/pages/index.ts b/x-pack/plugins/cloud_security_posture/public/pages/index.ts index 6e7ce20953d0cb..cc8fcae0a59a0b 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/index.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/index.ts @@ -7,6 +7,7 @@ export { Findings } from './findings'; export { Configurations } from './configurations'; -export * from './compliance_dashboard'; +export { ComplianceDashboard } from './compliance_dashboard'; export { Benchmarks } from './benchmarks'; export { Rules } from './rules'; +export { VulnerabilityDashboard } from './vulnerability_dashboard/vulnerability_dashboard'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.tsx new file mode 100644 index 00000000000000..a1cc1b39a2b882 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiPageHeader } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { VULNERABILITY_DASHBOARD_PAGE_HEADER } from '../compliance_dashboard/test_subjects'; +import { CloudPosturePageTitle } from '../../components/cloud_posture_page_title'; +import { CloudPosturePage } from '../../components/cloud_posture_page'; + +export const VulnerabilityDashboard = () => { + return ( + + + + + } + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts index c7b141d6c38787..66e31cbc479b90 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts @@ -57,7 +57,7 @@ export const generateMlInferencePipelineBody = ({ model, pipelineName, }: MlInferencePipelineParams): MlInferencePipeline => { - const inferenceType = Object.keys(model.inference_config)[0]; + const inferenceType = Object.keys(model.inference_config || {})[0]; const pipelineDefinition: MlInferencePipeline = { description: description ?? '', processors: [], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts index a41ddaa2726fb8..c635e6c7853191 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts @@ -53,7 +53,7 @@ export const NLP_DISPLAY_TITLES: Record = { export const isSupportedMLModel = (model: TrainedModelConfigResponse): boolean => { return ( - Object.keys(model.inference_config).some((key) => NLP_CONFIG_KEYS.includes(key)) || + Object.keys(model.inference_config || {}).some((key) => NLP_CONFIG_KEYS.includes(key)) || model.model_type === TRAINED_MODEL_TYPE.LANG_IDENT ); }; @@ -83,7 +83,8 @@ export const getMLType = (modelTypes: string[]): string => { export const getModelDisplayTitle = (type: string): string | undefined => NLP_DISPLAY_TITLES[type]; -export const isTextExpansionModel = (model: TrainedModel) => model.inference_config.text_expansion; +export const isTextExpansionModel = (model: TrainedModel): boolean => + Boolean(model.inference_config?.text_expansion); /** * Sort function for displaying a list of models. Promotes text_expansion models and sorts the rest by model ID. diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.test.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.test.ts index ed6aa16b26a355..3d68cf81f9e709 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.test.ts @@ -18,8 +18,8 @@ jest.mock('./fetch_analytics_collection', () => ({ fetchAnalyticsCollections: je describe('add analytics collection lib function', () => { const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + putBehavioralAnalytics: jest.fn(), }, }, asInternalUser: {}, @@ -34,7 +34,7 @@ describe('add analytics collection lib function', () => { }); it('should add analytics collection', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => ({ + mockClient.asCurrentUser.searchApplication.putBehavioralAnalytics.mockImplementation(() => ({ acknowledged: true, name: `example`, })); @@ -57,9 +57,8 @@ describe('add analytics collection lib function', () => { name: 'example', }); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - method: 'PUT', - path: '/_application/analytics/example', + expect(mockClient.asCurrentUser.searchApplication.putBehavioralAnalytics).toHaveBeenCalledWith({ + name: 'example', }); expect(mockDataViewsService.createAndSave).toHaveBeenCalledWith( @@ -74,7 +73,7 @@ describe('add analytics collection lib function', () => { }); it('should reject if analytics collection already exists', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => + mockClient.asCurrentUser.searchApplication.putBehavioralAnalytics.mockImplementation(() => Promise.reject({ meta: { body: { diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.ts index f85732136d4541..49a6903bde4e46 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/add_analytics_collection.ts @@ -22,14 +22,10 @@ interface CollectionsPutResponse { const createAnalyticsCollection = async ( client: IScopedClusterClient, name: string -): Promise => { - const response = await client.asCurrentUser.transport.request({ - method: 'PUT', - path: `/_application/analytics/${name}`, - }); - - return response; -}; +): Promise => + (await client.asCurrentUser.searchApplication.putBehavioralAnalytics({ + name, + })) as CollectionsPutResponse; const createDataView = async ( dataViewsService: DataViewsService, diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts index 06a08f1cc88484..0e34e396849066 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.test.ts @@ -14,8 +14,8 @@ import { deleteAnalyticsCollectionById } from './delete_analytics_collection'; describe('delete analytics collection lib function', () => { const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + deleteBehavioralAnalytics: jest.fn(), }, }, asInternalUser: {}, @@ -31,14 +31,15 @@ describe('delete analytics collection lib function', () => { deleteAnalyticsCollectionById(mockClient as unknown as IScopedClusterClient, 'example') ).resolves.toBeUndefined(); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - method: 'DELETE', - path: '/_application/analytics/example', + expect( + mockClient.asCurrentUser.searchApplication.deleteBehavioralAnalytics + ).toHaveBeenCalledWith({ + name: 'example', }); }); it('should throw an exception when analytics collection does not exist', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => + mockClient.asCurrentUser.searchApplication.deleteBehavioralAnalytics.mockImplementation(() => Promise.reject({ meta: { body: { diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts index c305d1a4b10317..24bdaae91d776b 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/delete_analytics_collection.ts @@ -10,16 +10,9 @@ import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { ErrorCode } from '../../../common/types/error_codes'; import { isResourceNotFoundException } from '../../utils/identify_exceptions'; -interface CollectionsDeleteResponse { - acknowledged: boolean; -} - export const deleteAnalyticsCollectionById = async (client: IScopedClusterClient, name: string) => { try { - await client.asCurrentUser.transport.request({ - method: 'DELETE', - path: `/_application/analytics/${name}`, - }); + await client.asCurrentUser.searchApplication.deleteBehavioralAnalytics({ name }); } catch (error) { if (isResourceNotFoundException(error)) { throw new Error(ErrorCode.ANALYTICS_COLLECTION_NOT_FOUND); diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.test.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.test.ts index 83ef91599f5546..bc565971bb38f8 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.test.ts @@ -12,8 +12,8 @@ import { fetchAnalyticsCollections } from './fetch_analytics_collection'; describe('fetch analytics collection lib function', () => { const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + getBehavioralAnalytics: jest.fn(), }, }, asInternalUser: {}, @@ -25,7 +25,7 @@ describe('fetch analytics collection lib function', () => { describe('fetch collections', () => { it('should return a list of analytics collections', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => + mockClient.asCurrentUser.searchApplication.getBehavioralAnalytics.mockImplementation(() => Promise.resolve({ example: { event_data_stream: { @@ -50,7 +50,7 @@ describe('fetch analytics collection lib function', () => { describe('fetch collection by Id', () => { it('should fetch analytics collection by Id', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => + mockClient.asCurrentUser.searchApplication.getBehavioralAnalytics.mockImplementation(() => Promise.resolve({ example: { event_data_stream: { diff --git a/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.ts b/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.ts index 8baff2c85de44d..09d04caee0281e 100644 --- a/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.ts +++ b/x-pack/plugins/enterprise_search/server/lib/analytics/fetch_analytics_collection.ts @@ -12,22 +12,13 @@ import { ErrorCode } from '../../../common/types/error_codes'; import { isResourceNotFoundException } from '../../utils/identify_exceptions'; -interface CollectionsListResponse { - [name: string]: { - event_data_stream: { - name: string; - }; - }; -} - export const fetchAnalyticsCollections = async ( client: IScopedClusterClient, query: string = '' ): Promise => { try { - const collections = await client.asCurrentUser.transport.request({ - method: 'GET', - path: `/_application/analytics/${query}`, + const collections = await client.asCurrentUser.searchApplication.getBehavioralAnalytics({ + name: query, }); return Object.keys(collections).map((value) => { diff --git a/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts index 069395baec777b..69c8a5895e37a6 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts +++ b/x-pack/plugins/enterprise_search/server/lib/ml/get_ml_model_deployment_status.ts @@ -27,10 +27,7 @@ export const getMlModelDeploymentStatus = async ( throw new Error('Machine Learning is not enabled'); } - // TODO: the ts-expect-error below should be removed once the correct typings are - // available in Kibana const modelDetailsRequest: MlGetTrainedModelsRequest = { - // @ts-expect-error @elastic-elasticsearch getTrainedModels types incorrect include: 'definition_status', model_id: modelName, }; @@ -43,8 +40,6 @@ export const getMlModelDeploymentStatus = async ( return getDefaultStatusReturn(MlModelDeploymentState.NotDeployed, modelName); } - // TODO - we can remove this cast to the extension once the new types are available - // in kibana that includes the fully_defined field const firstTrainedModelConfig = modelDetailsResponse.trained_model_configs ? (modelDetailsResponse.trained_model_configs[0] as MlTrainedModelConfigWithDefined) : (undefined as unknown as MlTrainedModelConfigWithDefined); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts index f637e5ac9901f9..cc59fcf32c2eeb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.test.ts @@ -26,8 +26,8 @@ describe('engines routes', () => { const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + list: jest.fn(), }, }, }; @@ -49,14 +49,10 @@ describe('engines routes', () => { }); it('GET search applications API creates request', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => ({})); + mockClient.asCurrentUser.searchApplication.list.mockImplementation(() => ({})); const request = { query: {} }; await mockRouter.callRoute({}); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - method: 'GET', - path: '/_application/search_application', - querystring: request.query, - }); + expect(mockClient.asCurrentUser.searchApplication.list).toHaveBeenCalledWith(request.query); expect(mockRouter.response.ok).toHaveBeenCalledWith({ body: {}, }); @@ -85,8 +81,8 @@ describe('engines routes', () => { let mockRouter: MockRouter; const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + get: jest.fn(), }, }, }; @@ -109,14 +105,13 @@ describe('engines routes', () => { }); it('GET search application API creates request', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => ({})); + mockClient.asCurrentUser.searchApplication.get.mockImplementation(() => ({})); await mockRouter.callRoute({ params: { engine_name: 'engine-name' }, }); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - method: 'GET', - path: '/_application/search_application/engine-name', + expect(mockClient.asCurrentUser.searchApplication.get).toHaveBeenCalledWith({ + name: 'engine-name', }); const mock = jest.fn(); @@ -156,8 +151,8 @@ describe('engines routes', () => { let mockRouter: MockRouter; const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + put: jest.fn(), }, }, }; @@ -180,7 +175,7 @@ describe('engines routes', () => { }); it('PUT - Upsert API creates request - create', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => ({ + mockClient.asCurrentUser.searchApplication.put.mockImplementation(() => ({ acknowledged: true, })); @@ -193,13 +188,14 @@ describe('engines routes', () => { }, query: { create: true }, }); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - body: { + expect(mockClient.asCurrentUser.searchApplication.put).toHaveBeenCalledWith({ + create: true, + name: 'engine-name', + search_application: { indices: ['test-indices-1'], + name: 'engine-name', + updated_at_millis: expect.any(Number), }, - method: 'PUT', - path: '/_application/search_application/engine-name', - querystring: { create: true }, }); const mock = jest.fn(); const mockResponse = mock({ result: 'created' }); @@ -211,7 +207,7 @@ describe('engines routes', () => { }); }); it('PUT - Upsert API creates request - update', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => ({ + mockClient.asCurrentUser.searchApplication.put.mockImplementation(() => ({ acknowledged: true, })); @@ -223,13 +219,13 @@ describe('engines routes', () => { engine_name: 'engine-name', }, }); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - body: { + expect(mockClient.asCurrentUser.searchApplication.put).toHaveBeenCalledWith({ + name: 'engine-name', + search_application: { indices: ['test-indices-1'], + name: 'engine-name', + updated_at_millis: expect.any(Number), }, - method: 'PUT', - path: '/_application/search_application/engine-name', - querystring: {}, }); const mock = jest.fn(); const mockResponse = mock({ result: 'updated' }); @@ -268,7 +264,7 @@ describe('engines routes', () => { }); it('returns 409 when upsert create search application throws error', async () => { - (mockClient.asCurrentUser.transport.request as jest.Mock).mockRejectedValueOnce({ + (mockClient.asCurrentUser.searchApplication.put as jest.Mock).mockRejectedValueOnce({ meta: { body: { error: { @@ -298,8 +294,8 @@ describe('engines routes', () => { let mockRouter: MockRouter; const mockClient = { asCurrentUser: { - transport: { - request: jest.fn(), + searchApplication: { + delete: jest.fn(), }, }, }; @@ -322,7 +318,7 @@ describe('engines routes', () => { }); it('Delete API creates request', async () => { - mockClient.asCurrentUser.transport.request.mockImplementation(() => ({ + mockClient.asCurrentUser.searchApplication.delete.mockImplementation(() => ({ acknowledged: true, })); @@ -331,9 +327,8 @@ describe('engines routes', () => { engine_name: 'engine-name', }, }); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - method: 'DELETE', - path: '_application/search_application/engine-name', + expect(mockClient.asCurrentUser.searchApplication.delete).toHaveBeenCalledWith({ + name: 'engine-name', }); expect(mockRouter.response.ok).toHaveBeenCalledWith({ body: { @@ -448,7 +443,7 @@ describe('engines routes', () => { describe('GET /internal/enterprise_search/engines/{engine_name}/field_capabilities', () => { let mockRouter: MockRouter; const mockClient = { - asCurrentUser: { transport: { request: jest.fn() } }, + asCurrentUser: { searchApplication: { get: jest.fn() } }, }; const mockCore = { elasticsearch: { client: mockClient }, @@ -485,16 +480,17 @@ describe('engines routes', () => { name: 'unit-test', }; - (mockClient.asCurrentUser.transport.request as jest.Mock).mockResolvedValueOnce(engineResult); + (mockClient.asCurrentUser.searchApplication.get as jest.Mock).mockResolvedValueOnce( + engineResult + ); (fetchEngineFieldCapabilities as jest.Mock).mockResolvedValueOnce(fieldCapabilitiesResult); await mockRouter.callRoute({ params: { engine_name: 'unit-test' }, }); - expect(mockClient.asCurrentUser.transport.request).toHaveBeenCalledWith({ - method: 'GET', - path: '/_application/search_application/unit-test', + expect(mockClient.asCurrentUser.searchApplication.get).toHaveBeenCalledWith({ + name: 'unit-test', }); expect(fetchEngineFieldCapabilities).toHaveBeenCalledWith(mockClient, engineResult); expect(mockRouter.response.ok).toHaveBeenCalledWith({ @@ -503,7 +499,7 @@ describe('engines routes', () => { }); }); it('returns 404 when fetch engine throws a not found exception', async () => { - (mockClient.asCurrentUser.transport.request as jest.Mock).mockRejectedValueOnce({ + (mockClient.asCurrentUser.searchApplication.get as jest.Mock).mockRejectedValueOnce({ meta: { body: { error: { @@ -529,7 +525,7 @@ describe('engines routes', () => { }); }); it('returns error when fetch engine returns an unknown error', async () => { - (mockClient.asCurrentUser.transport.request as jest.Mock).mockRejectedValueOnce({ + (mockClient.asCurrentUser.searchApplication.get as jest.Mock).mockRejectedValueOnce({ body: { attributes: { error_code: 'unknown_error', diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts index 7d49f5b879a261..64290b3988c882 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/engines.ts @@ -41,13 +41,9 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) { }, elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const engines = await client.asCurrentUser.transport.request( - { - method: 'GET', - path: `/_application/search_application`, - querystring: request.query, - } - ); + const engines = (await client.asCurrentUser.searchApplication.list( + request.query + )) as EnterpriseSearchEnginesResponse; return response.ok({ body: engines }); }) @@ -64,10 +60,9 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) { }, elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const engine = await client.asCurrentUser.transport.request({ - method: 'GET', - path: `/_application/search_application/${request.params.engine_name}`, - }); + const engine = (await client.asCurrentUser.searchApplication.get({ + name: request.params.engine_name, + })) as EnterpriseSearchEngine; const indicesStats = await fetchIndicesStats(client, engine.indices); return response.ok({ body: { ...engine, indices: indicesStats } }); @@ -93,13 +88,16 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) { elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; try { - const engine = - await client.asCurrentUser.transport.request({ - body: { indices: request.body.indices }, - method: 'PUT', - path: `/_application/search_application/${request.params.engine_name}`, - querystring: request.query, - }); + const engine = (await client.asCurrentUser.searchApplication.put({ + ...request.query, + name: request.params.engine_name, + search_application: { + indices: request.body.indices, + name: request.params.engine_name, + updated_at_millis: Date.now(), + }, + })) as EnterpriseSearchEngineUpsertResponse; + return response.ok({ body: engine }); } catch (error) { if (isVersionConflictEngineException(error)) { @@ -132,10 +130,10 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) { }, elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; - const engine = await client.asCurrentUser.transport.request({ - method: 'DELETE', - path: `_application/search_application/${request.params.engine_name}`, - }); + const engine = (await client.asCurrentUser.searchApplication.delete({ + name: request.params.engine_name, + })) as AcknowledgedResponseBase; + return response.ok({ body: engine }); }) ); @@ -155,8 +153,8 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) { elasticsearchErrorHandler(log, async (context, request, response) => { const { client } = (await context.core).elasticsearch; const engines = await client.asCurrentUser.search({ - index: request.params.engine_name, ...request.body, + index: request.params.engine_name, }); return response.ok({ body: engines }); }) @@ -193,13 +191,11 @@ export function registerEnginesRoutes({ log, router }: RouteDependencies) { }, elasticsearchErrorHandler(log, async (context, request, response) => { try { - const engineName = decodeURIComponent(request.params.engine_name); const { client } = (await context.core).elasticsearch; - const engine = await client.asCurrentUser.transport.request({ - method: 'GET', - path: `/_application/search_application/${engineName}`, - }); + const engine = (await client.asCurrentUser.searchApplication.get({ + name: request.params.engine_name, + })) as EnterpriseSearchEngine; const data = await fetchEngineFieldCapabilities(client, engine); return response.ok({ diff --git a/x-pack/plugins/event_log/server/es/init.test.ts b/x-pack/plugins/event_log/server/es/init.test.ts index 5165dc69cd7efc..7280849a8cac01 100644 --- a/x-pack/plugins/event_log/server/es/init.test.ts +++ b/x-pack/plugins/event_log/server/es/init.test.ts @@ -455,7 +455,8 @@ describe('parseIndexAliases', () => { }); }); -describe('retries', () => { +// FLAKY: https://github.com/elastic/kibana/issues/156061 +describe.skip('retries', () => { let esContext = contextMock.create(); // set up context APIs to return defaults indicating already created beforeEach(() => { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 4573b5ef7f21b1..1192e59b8af98f 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -703,11 +703,7 @@ const updateExistingDataStream = async ({ } // Trigger a rollover if the index mode or source type has changed - if ( - currentIndexMode !== settings?.index?.mode || - // @ts-expect-error Property 'mode' does not exist on type 'MappingSourceField' - currentSourceType !== mappings?._source?.mode - ) { + if (currentIndexMode !== settings?.index?.mode || currentSourceType !== mappings?._source?.mode) { logger.info( `Index mode or source type has changed for ${dataStreamName}, triggering a rollover` ); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index 6f4724642e9f3b..deb8c0a313f301 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -767,11 +767,13 @@ async function handleTransformInstall({ { logger, additionalResponseStatuses: [400] } ); if (Array.isArray(transformStats.transforms) && transformStats.transforms.length === 1) { - // @ts-expect-error TransformGetTransformStatsTransformStats should have 'health' const transformHealth = transformStats.transforms[0].health; if ( + transformHealth && transformHealth.status === 'red' && + // @ts-expect-error TransformGetTransformStatsTransformStatsHealth should have 'issues' Array.isArray(transformHealth.issues) && + // @ts-expect-error TransformGetTransformStatsTransformStatsHealth should have 'issues' transformHealth.issues.find( (i: { issue: string }) => i.issue === 'Privileges check failed' ) diff --git a/x-pack/plugins/infra/common/source_configuration/source_configuration.ts b/x-pack/plugins/infra/common/source_configuration/source_configuration.ts index 3b1694abdadc78..8881b5fab5f5fb 100644 --- a/x-pack/plugins/infra/common/source_configuration/source_configuration.ts +++ b/x-pack/plugins/infra/common/source_configuration/source_configuration.ts @@ -19,25 +19,6 @@ import { omit } from 'lodash'; import * as rt from 'io-ts'; -import moment from 'moment'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { chain } from 'fp-ts/lib/Either'; - -export const TimestampFromString = new rt.Type( - 'TimestampFromString', - (input): input is number => typeof input === 'number', - (input, context) => - pipe( - rt.string.validate(input, context), - chain((stringInput) => { - const momentValue = moment(stringInput); - return momentValue.isValid() - ? rt.success(momentValue.valueOf()) - : rt.failure(stringInput, context); - }) - ), - (output) => new Date(output).toISOString() -); /** * Source configuration config file properties. @@ -246,21 +227,3 @@ export const SourceResponseRuntimeType = rt.type({ }); export type SourceResponse = rt.TypeOf; - -/** - * Saved object type with metadata - */ - -export const SourceConfigurationSavedObjectRuntimeType = rt.intersection([ - rt.type({ - id: rt.string, - attributes: SavedSourceConfigurationRuntimeType, - }), - rt.partial({ - version: rt.string, - updated_at: TimestampFromString, - }), -]); - -export interface SourceConfigurationSavedObject - extends rt.TypeOf {} diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx index 32cc6e56d861f0..e5dcc75ce9c4e9 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details.stories.tsx @@ -5,15 +5,15 @@ * 2.0. */ +import React from 'react'; import { EuiButton, EuiCard } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n-react'; import type { Meta, Story } from '@storybook/react/types-6-0'; -import React from 'react'; import { i18n } from '@kbn/i18n'; import { DecorateWithKibanaContext } from './asset_details.story_decorators'; - -import { AssetDetails, FlyoutTabIds, type AssetDetailsProps } from './asset_details'; +import { AssetDetails } from './asset_details'; import { decorateWithGlobalStorybookThemeProviders } from '../../test_utils/use_global_storybook_theme'; +import { FlyoutTabIds, type AssetDetailsProps } from './types'; export default { title: 'infra/Asset Details View/Asset Details Embeddable', @@ -42,20 +42,12 @@ export default { memoryTotal: 34359738368, }, nodeType: 'host', - closeFlyout: () => {}, - onTabClick: () => {}, - renderedTabsSet: { current: new Set(['metadata']) }, currentTimeRange: { interval: '1s', from: 1683630468, to: 1683630469, }, - hostFlyoutOpen: { - clickedItemId: 'host1-macos', - selectedTabId: 'metadata', - searchFilter: '', - metadataSearch: '', - }, + selectedTabId: 'metadata', tabs: [ { id: FlyoutTabIds.METADATA, @@ -73,7 +65,7 @@ export default { }, ], links: ['apmServices', 'uptime'], - }, + } as AssetDetailsProps, } as Meta; const Template: Story = (args) => { @@ -91,26 +83,32 @@ const FlyoutTemplate: Story = (args) => { > Open flyout - + ); }; export const DefaultAssetDetailsWithMetadataTabSelected = Template.bind({}); DefaultAssetDetailsWithMetadataTabSelected.args = { - showActionsColumn: true, + overrides: { + metadata: { + showActionsColumn: true, + }, + }, }; export const AssetDetailsWithMetadataTabSelectedWithPersistedSearch = Template.bind({}); AssetDetailsWithMetadataTabSelectedWithPersistedSearch.args = { - showActionsColumn: true, - hostFlyoutOpen: { - clickedItemId: 'host1-macos', - selectedTabId: 'metadata', - searchFilter: '', - metadataSearch: 'ip', + overrides: { + metadata: { + showActionsColumn: true, + query: 'ip', + }, }, - setHostFlyoutState: () => {}, + activeTabId: 'metadata', + onTabsStateChange: () => {}, }; export const AssetDetailsWithMetadataWithoutActions = Template.bind({}); @@ -120,22 +118,21 @@ export const AssetDetailsWithMetadataWithoutLinks = Template.bind({}); AssetDetailsWithMetadataWithoutLinks.args = { links: [] }; export const AssetDetailsAsFlyout = FlyoutTemplate.bind({}); -AssetDetailsAsFlyout.args = { showInFlyout: true }; +AssetDetailsAsFlyout.args = { + renderMode: { + showInFlyout: true, + closeFlyout: () => {}, + }, +}; export const AssetDetailsWithProcessesTabSelected = Template.bind({}); AssetDetailsWithProcessesTabSelected.args = { - renderedTabsSet: { current: new Set(['processes']) }, + activeTabId: 'processes', currentTimeRange: { interval: '1s', from: 1683630468, to: 1683630469, }, - hostFlyoutOpen: { - clickedItemId: 'host1-macos', - selectedTabId: 'processes', - searchFilter: '', - metadataSearch: '', - }, }; export const AssetDetailsWithMetadataTabOnly = Template.bind({}); diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details.story_decorators.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details.story_decorators.tsx index ba75f8ed6329f4..7abc2d5937bef5 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details.story_decorators.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details.story_decorators.tsx @@ -9,6 +9,7 @@ import type { StoryContext } from '@storybook/react'; import React from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { of } from 'rxjs'; import { SourceProvider } from '../../containers/metrics_source'; export const DecorateWithKibanaContext = ( @@ -172,7 +173,7 @@ export const DecorateWithKibanaContext = {} }, + currentAppId$: of('infra'), navigateToUrl: () => {}, }, dataViews: { create: () => {} }, diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx index 2037b536d0f692..9ea1a5163059ae 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details.tsx @@ -5,188 +5,77 @@ * 2.0. */ -import React, { useState } from 'react'; -import { - EuiFlyout, - EuiFlyoutHeader, - EuiTitle, - EuiFlyoutBody, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTabs, - EuiTab, - useEuiTheme, -} from '@elastic/eui'; -import { css } from '@emotion/react'; -import { LinkToUptime } from './links/link_to_uptime'; -import { LinkToApmServices } from './links/link_to_apm_services'; -import type { HostNodeRow } from './types'; +import React from 'react'; +import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody } from '@elastic/eui'; +import type { AssetDetailsProps, RenderMode } from './types'; import type { InventoryItemType } from '../../../common/inventory_models/types'; -import type { SetNewHostFlyoutOpen } from '../../pages/metrics/hosts/hooks/use_host_flyout_open_url_state'; -import { AssetDetailsTabContent } from './tabs_content/tabs_content'; +import { TabContent } from './tab_content/tab_content'; +import { Header } from './header/header'; +import { TabSwitcherProvider } from './hooks/use_tab_switcher'; -export enum FlyoutTabIds { - METADATA = 'metadata', - PROCESSES = 'processes', -} - -export type TabIds = `${FlyoutTabIds}`; - -export interface Tab { - id: FlyoutTabIds; - name: string; - 'data-test-subj': string; -} +// Setting host as default as it will be the only supported type for now +const NODE_TYPE = 'host' as InventoryItemType; -export interface AssetDetailsProps { - node: HostNodeRow; - nodeType: InventoryItemType; - closeFlyout: () => void; - renderedTabsSet: React.MutableRefObject>; - currentTimeRange: { - interval: string; - from: number; - to: number; - }; - tabs: Tab[]; - hostFlyoutOpen?: { - clickedItemId: string; - selectedTabId: TabIds; - searchFilter: string; - metadataSearch: string; - }; - setHostFlyoutState?: SetNewHostFlyoutOpen; - onTabClick?: (tab: Tab) => void; - links?: Array<'uptime' | 'apmServices'>; - showInFlyout?: boolean; - showActionsColumn?: boolean; +interface ContentTemplateProps { + header: React.ReactElement; + body: React.ReactElement; + renderMode: RenderMode; } -// Setting host as default as it will be the only supported type for now -const NODE_TYPE = 'host' as InventoryItemType; +const ContentTemplate = ({ header, body, renderMode }: ContentTemplateProps) => { + return renderMode.showInFlyout ? ( + + {header} + {body} + + ) : ( + <> + {header} + {body} + + ); +}; export const AssetDetails = ({ node, - closeFlyout, - onTabClick, - renderedTabsSet, currentTimeRange, - hostFlyoutOpen, - setHostFlyoutState, + activeTabId, + overrides, + onTabsStateChange, tabs, - showInFlyout, links, - showActionsColumn, nodeType = NODE_TYPE, + renderMode = { + showInFlyout: false, + }, }: AssetDetailsProps) => { - const { euiTheme } = useEuiTheme(); - const [selectedTabId, setSelectedTabId] = useState('metadata'); - - const onTabSelectClick = (tab: Tab) => { - renderedTabsSet.current.add(tab.id); // On a tab click, mark the tab content as allowed to be rendered - setSelectedTabId(tab.id); - }; - - const tabEntries = tabs.map((tab) => ( - (onTabClick ? onTabClick(tab) : onTabSelectClick(tab))} - isSelected={tab.id === hostFlyoutOpen?.selectedTabId ?? selectedTabId} - > - {tab.name} - - )); - - const linksMapping = { - apmServices: ( - - - - ), - uptime: ( - - - - ), - }; - - const headerLinks = links?.map((link) => linksMapping[link]); - - if (!showInFlyout) { - return ( - <> - - - -

{node.name}

-
-
- {links && headerLinks} -
- - - {tabEntries} - - - - ); - } - return ( - - - - - -

{node.name}

-
-
- {links && headerLinks} -
- - - {tabEntries} - -
- - - -
+ 0 ? activeTabId ?? tabs[0].id : undefined} + > + + } + body={ + + } + renderMode={renderMode} + /> + ); }; diff --git a/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx b/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx index 5549668665c130..4fca0718864f8d 100644 --- a/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/asset_details_embeddable.tsx @@ -14,7 +14,7 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { CoreProviders } from '../../apps/common_providers'; import { InfraClientStartDeps, InfraClientStartExports } from '../../types'; import { LazyAssetDetailsWrapper } from './lazy_asset_details_wrapper'; -import type { AssetDetailsProps } from './asset_details'; +import type { AssetDetailsProps } from './types'; export const ASSET_DETAILS_EMBEDDABLE = 'ASSET_DETAILS_EMBEDDABLE'; @@ -71,18 +71,15 @@ export class AssetDetailsEmbeddable extends Embeddable
diff --git a/x-pack/plugins/infra/public/components/asset_details/header/header.tsx b/x-pack/plugins/infra/public/components/asset_details/header/header.tsx new file mode 100644 index 00000000000000..6937b8354be6b4 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/header/header.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiTitle, + EuiSpacer, + EuiFlexItem, + EuiFlexGroup, + EuiTabs, + EuiTab, + useEuiTheme, + useEuiMaxBreakpoint, + useEuiMinBreakpoint, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { EuiShowFor } from '@elastic/eui'; +import type { AssetDetailsProps } from '../types'; +import { LinkToApmServices } from '../links/link_to_apm_services'; +import { LinkToUptime } from '../links/link_to_uptime'; +import { useTabSwitcherContext } from '../hooks/use_tab_switcher'; +import type { TabIds } from '../types'; +type Props = Pick< + AssetDetailsProps, + 'node' | 'nodeType' | 'links' | 'tabs' | 'onTabsStateChange' +> & { + compact: boolean; +}; + +export const Header = ({ nodeType, node, tabs, links, compact, onTabsStateChange }: Props) => { + const { euiTheme } = useEuiTheme(); + const { showTab, activeTabId } = useTabSwitcherContext(); + + const onTabClick = (tabId: TabIds) => { + if (onTabsStateChange) { + onTabsStateChange({ activeTabId: tabId }); + } + + showTab(tabId); + }; + + const tabEntries = tabs.map((tab) => ( + onTabClick(tab.id)} + isSelected={tab.id === activeTabId} + > + {tab.name} + + )); + + const linkComponent = { + apmServices: , + uptime: , + }; + + const headerLinks = links?.map((link, index) => ( + + {linkComponent[link]} + + )); + + return ( + <> + + {!compact && ( + + + + )} + + +

{node.name}

+
+
+ + + {headerLinks} + + +
+ + + {tabEntries} + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx b/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx new file mode 100644 index 00000000000000..f6f7757bb062f8 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/hooks/use_tab_switcher.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState } from 'react'; +import createContainer from 'constate'; +import { useLazyRef } from '../../../hooks/use_lazy_ref'; +import type { TabIds } from '../types'; + +export function useTabSwitcher({ initialActiveTabId }: { initialActiveTabId?: TabIds }) { + const [activeTabId, setActiveTabId] = useState(initialActiveTabId); + + // This set keeps track of which tabs content have been rendered the first time. + // We need it in order to load a tab content only if it gets clicked, and then keep it in the DOM for performance improvement. + const renderedTabsSet = useLazyRef(() => new Set([initialActiveTabId])); + + const showTab = (tabId: TabIds) => { + renderedTabsSet.current.add(tabId); // On a tab click, mark the tab content as allowed to be rendered + setActiveTabId(tabId); + }; + + return { + activeTabId, + renderedTabsSet, + showTab, + }; +} + +export const TabSwitcher = createContainer(useTabSwitcher); +export const [TabSwitcherProvider, useTabSwitcherContext] = TabSwitcher; diff --git a/x-pack/plugins/infra/public/components/asset_details/lazy_asset_details_wrapper.tsx b/x-pack/plugins/infra/public/components/asset_details/lazy_asset_details_wrapper.tsx index e6eec56ddfe87e..02f12ca6598218 100644 --- a/x-pack/plugins/infra/public/components/asset_details/lazy_asset_details_wrapper.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/lazy_asset_details_wrapper.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; -import type { AssetDetailsProps } from './asset_details'; +import type { AssetDetailsProps } from './types'; const AssetDetails = React.lazy(() => import('./asset_details')); diff --git a/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.stories.tsx index 323369a06f4e03..fb8f8a4a7a0533 100644 --- a/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/links/link_to_apm_services.stories.tsx @@ -10,12 +10,13 @@ import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { Meta, Story } from '@storybook/react/types-6-0'; import React from 'react'; +import { of } from 'rxjs'; import { decorateWithGlobalStorybookThemeProviders } from '../../../test_utils/use_global_storybook_theme'; import { LinkToApmServices, type LinkToApmServicesProps } from './link_to_apm_services'; const mockServices = { application: { - currentAppId$: { title: 'infra', subscribe: () => {} }, + currentAppId$: of('infra'), navigateToUrl: () => {}, }, http: { diff --git a/x-pack/plugins/infra/public/components/asset_details/tab_content/tab_content.tsx b/x-pack/plugins/infra/public/components/asset_details/tab_content/tab_content.tsx new file mode 100644 index 00000000000000..506306760bc853 --- /dev/null +++ b/x-pack/plugins/infra/public/components/asset_details/tab_content/tab_content.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useTabSwitcherContext } from '../hooks/use_tab_switcher'; +import Metadata from '../tabs/metadata/metadata'; +import { Processes } from '../tabs/processes/processes'; +import { FlyoutTabIds, type TabState, type AssetDetailsProps } from '../types'; + +type Props = Pick< + AssetDetailsProps, + 'currentTimeRange' | 'node' | 'nodeType' | 'overrides' | 'onTabsStateChange' +>; + +export const TabContent = ({ + overrides, + currentTimeRange, + node, + nodeType, + onTabsStateChange, +}: Props) => { + const onChange = (state: TabState) => { + if (!onTabsStateChange) { + return; + } + + onTabsStateChange(state); + }; + + return ( + <> + + onChange({ metadata: { query } })} + /> + + + onChange({ processes: { query } })} + /> + + + ); +}; + +const TabPanel = ({ + activeWhen, + children, +}: { + activeWhen: FlyoutTabIds; + children: React.ReactNode; +}) => { + const { renderedTabsSet, activeTabId } = useTabSwitcherContext(); + + return renderedTabsSet.current.has(activeWhen) ? ( + + ) : null; +}; diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/add_metadata_filter_button.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx similarity index 92% rename from x-pack/plugins/infra/public/components/asset_details/metadata/add_metadata_filter_button.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx index 8a89e8faecb77d..31d978235be323 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/add_metadata_filter_button.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/add_metadata_filter_button.tsx @@ -8,9 +8,9 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; -import { useMetricsDataViewContext } from '../../../pages/metrics/hosts/hooks/use_data_view'; -import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; -import { useUnifiedSearchContext } from '../../../pages/metrics/hosts/hooks/use_unified_search'; +import { useMetricsDataViewContext } from '../../../../pages/metrics/hosts/hooks/use_data_view'; +import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; +import { useUnifiedSearchContext } from '../../../../pages/metrics/hosts/hooks/use_unified_search'; import { buildMetadataFilter } from './build_metadata_filter'; interface AddMetadataFilterButtonProps { diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/build_metadata_filter.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/build_metadata_filter.ts similarity index 100% rename from x-pack/plugins/infra/public/components/asset_details/metadata/build_metadata_filter.ts rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/build_metadata_filter.ts diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/lazy_metadata_wrapper.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/lazy_metadata_wrapper.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/asset_details/metadata/lazy_metadata_wrapper.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/lazy_metadata_wrapper.tsx diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/metadata.test.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx similarity index 92% rename from x-pack/plugins/infra/public/components/asset_details/metadata/metadata.test.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx index df2242dda3bb0e..2373d0008d08d7 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/metadata.test.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx @@ -8,14 +8,14 @@ import React from 'react'; import { Metadata, type MetadataProps } from './metadata'; -import { useMetadata } from '../hooks/use_metadata'; -import { useSourceContext } from '../../../containers/metrics_source'; +import { useMetadata } from '../../hooks/use_metadata'; +import { useSourceContext } from '../../../../containers/metrics_source'; import { render } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -jest.mock('../../../containers/metrics_source'); -jest.mock('../hooks/use_metadata'); +jest.mock('../../../../containers/metrics_source'); +jest.mock('../../hooks/use_metadata'); const metadataProps: MetadataProps = { currentTimeRange: { diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/metadata.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx similarity index 79% rename from x-pack/plugins/infra/public/components/asset_details/metadata/metadata.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx index 2cf7fce4c9ce29..09217bc7431495 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/metadata.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata.tsx @@ -9,14 +9,14 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { InventoryItemType } from '../../../../common/inventory_models/types'; -import { findInventoryModel } from '../../../../common/inventory_models'; -import type { MetricsTimeInput } from '../../../pages/metrics/metric_detail/hooks/use_metrics_time'; -import { useMetadata } from '../hooks/use_metadata'; -import { useSourceContext } from '../../../containers/metrics_source'; +import type { InventoryItemType } from '../../../../../common/inventory_models/types'; +import { findInventoryModel } from '../../../../../common/inventory_models'; +import type { MetricsTimeInput } from '../../../../pages/metrics/metric_detail/hooks/use_metrics_time'; +import { useMetadata } from '../../hooks/use_metadata'; +import { useSourceContext } from '../../../../containers/metrics_source'; import { Table } from './table'; import { getAllFields } from './utils'; -import type { HostNodeRow } from '../types'; +import type { HostNodeRow } from '../../types'; export interface MetadataSearchUrlState { metadataSearchUrlState: string; @@ -28,15 +28,17 @@ export interface MetadataProps { node: HostNodeRow; nodeType: InventoryItemType; showActionsColumn?: boolean; - persistMetadataSearchToUrlState?: MetadataSearchUrlState; + search?: string; + onSearchChange?: (query: string) => void; } export const Metadata = ({ node, currentTimeRange, nodeType, - showActionsColumn, - persistMetadataSearchToUrlState, + search, + showActionsColumn = false, + onSearchChange, }: MetadataProps) => { const nodeId = node.name; const inventoryModel = findInventoryModel(nodeType); @@ -81,7 +83,8 @@ export const Metadata = ({ return ( { node={this.input.node} nodeType={this.input.nodeType} showActionsColumn={this.input.showActionsColumn} - persistMetadataSearchToUrlState={this.input.persistMetadataSearchToUrlState} + onSearchChange={this.input.onSearchChange} + search={this.input.search} /> diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/metadata_embeddable_factory.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata_embeddable_factory.ts similarity index 99% rename from x-pack/plugins/infra/public/components/asset_details/metadata/metadata_embeddable_factory.ts rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata_embeddable_factory.ts index e50d59ac51b0e7..709bc49140f2fb 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/metadata_embeddable_factory.ts +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/metadata_embeddable_factory.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import type { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public'; -import type { InfraClientStartServicesAccessor } from '../../../types'; +import type { InfraClientStartServicesAccessor } from '../../../../types'; import { MetadataEmbeddable, MetadataEmbeddableInput, diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/table.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/table.stories.tsx similarity index 98% rename from x-pack/plugins/infra/public/components/asset_details/metadata/table.stories.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/table.stories.tsx index c3d4c00d2cbda4..48c17b35c70fec 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/table.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/table.stories.tsx @@ -10,7 +10,7 @@ import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { Meta, Story } from '@storybook/react/types-6-0'; import React from 'react'; -import { decorateWithGlobalStorybookThemeProviders } from '../../../test_utils/use_global_storybook_theme'; +import { decorateWithGlobalStorybookThemeProviders } from '../../../../test_utils/use_global_storybook_theme'; import { Table, Props } from './table'; const mockServices = { diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/table.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/table.tsx similarity index 85% rename from x-pack/plugins/infra/public/components/asset_details/metadata/table.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/table.tsx index 8497552d6ddd95..6be4445c83d28e 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/table.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/table.tsx @@ -21,7 +21,6 @@ import useToggle from 'react-use/lib/useToggle'; import { debounce } from 'lodash'; import { Query } from '@elastic/eui'; import { AddMetadataFilterButton } from './add_metadata_filter_button'; -import { MetadataSearchUrlState } from './metadata'; interface Row { name: string; @@ -32,7 +31,8 @@ export interface Props { rows: Row[]; loading: boolean; showActionsColumn?: boolean; - persistMetadataSearchToUrlState?: MetadataSearchUrlState; + search?: string; + onSearchChange?: (query: string) => void; } interface SearchErrorType { @@ -65,10 +65,9 @@ const LOADING = i18n.translate('xpack.infra.metadataEmbeddable.loading', { defaultMessage: 'Loading...', }); -export const Table = (props: Props) => { - const { rows, loading, showActionsColumn } = props; +export const Table = ({ loading, rows, onSearchChange, search, showActionsColumn }: Props) => { const [searchError, setSearchError] = useState(null); - const [metadataSearch, setMetadataSearch] = useState(''); + const [metadataSearch, setMetadataSearch] = useState(search); const defaultColumns = useMemo( () => [ @@ -93,13 +92,12 @@ export const Table = (props: Props) => { const debouncedSearchOnChange = useMemo( () => debounce<(queryText: string) => void>((queryText) => { - return props.persistMetadataSearchToUrlState - ? props.persistMetadataSearchToUrlState.setMetadataSearchUrlState({ - metadataSearch: String(queryText) ?? '', - }) - : setMetadataSearch(String(queryText) ?? ''); + if (onSearchChange) { + onSearchChange(queryText); + } + setMetadataSearch(queryText); }, 500), - [props.persistMetadataSearchToUrlState] + [onSearchChange] ); const searchBarOnChange = useCallback( @@ -114,7 +112,7 @@ export const Table = (props: Props) => { [debouncedSearchOnChange] ); - const search: EuiSearchBarProps = { + const searchBar: EuiSearchBarProps = { onChange: searchBarOnChange, box: { 'data-test-subj': 'infraHostMetadataSearchBarInput', @@ -122,13 +120,7 @@ export const Table = (props: Props) => { schema: true, placeholder: SEARCH_PLACEHOLDER, }, - query: props.persistMetadataSearchToUrlState - ? props.persistMetadataSearchToUrlState.metadataSearchUrlState - ? Query.parse(props.persistMetadataSearchToUrlState.metadataSearchUrlState) - : Query.MATCH_ALL - : metadataSearch - ? Query.parse(metadataSearch) - : Query.MATCH_ALL, + query: metadataSearch ? Query.parse(metadataSearch) : Query.MATCH_ALL, }; const columns = useMemo( @@ -159,7 +151,7 @@ export const Table = (props: Props) => { columns={columns} items={rows} rowProps={{ className: 'euiTableRow-hasActions' }} - search={search} + search={searchBar} loading={loading} error={searchError ? `${searchError.message}` : ''} message={ diff --git a/x-pack/plugins/infra/public/components/asset_details/metadata/utils.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/utils.ts similarity index 97% rename from x-pack/plugins/infra/public/components/asset_details/metadata/utils.ts rename to x-pack/plugins/infra/public/components/asset_details/tabs/metadata/utils.ts index e8dd94df751e81..f277eb1c337e5a 100644 --- a/x-pack/plugins/infra/public/components/asset_details/metadata/utils.ts +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/metadata/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { InfraMetadata } from '../../../../common/http_api'; +import type { InfraMetadata } from '../../../../../common/http_api'; export const getAllFields = (metadata: InfraMetadata | null) => { if (!metadata?.info) return []; diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/parse_search_string.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/parse_search_string.ts similarity index 100% rename from x-pack/plugins/infra/public/components/asset_details/processes/parse_search_string.ts rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/parse_search_string.ts diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/processes.stories.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx similarity index 97% rename from x-pack/plugins/infra/public/components/asset_details/processes/processes.stories.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx index 42e04737509d67..09b4d30147c17f 100644 --- a/x-pack/plugins/infra/public/components/asset_details/processes/processes.stories.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.stories.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { DecorateWithKibanaContext } from './processes.story_decorators'; import { Processes, type ProcessesProps } from './processes'; -import { decorateWithGlobalStorybookThemeProviders } from '../../../test_utils/use_global_storybook_theme'; +import { decorateWithGlobalStorybookThemeProviders } from '../../../../test_utils/use_global_storybook_theme'; export default { title: 'infra/Asset Details View/Components/Processes', diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/processes.story_decorators.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.story_decorators.tsx similarity index 99% rename from x-pack/plugins/infra/public/components/asset_details/processes/processes.story_decorators.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.story_decorators.tsx index 29e4b4b79faff3..4310467a51aec1 100644 --- a/x-pack/plugins/infra/public/components/asset_details/processes/processes.story_decorators.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.story_decorators.tsx @@ -12,7 +12,7 @@ import React from 'react'; import { useParameter } from '@storybook/addons'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { SourceProvider } from '../../../containers/metrics_source'; +import { SourceProvider } from '../../../../containers/metrics_source'; export const DecorateWithKibanaContext = ( wrappedStory: () => StoryFnReactReturnType, diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/processes.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx similarity index 88% rename from x-pack/plugins/infra/public/components/asset_details/processes/processes.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx index c014e9a3c457a4..cb6660c64a926a 100644 --- a/x-pack/plugins/infra/public/components/asset_details/processes/processes.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes.tsx @@ -21,22 +21,22 @@ import { parseSearchString } from './parse_search_string'; import { ProcessesTable } from './processes_table'; import { STATE_NAMES } from './states'; import { SummaryTable } from './summary_table'; -import { TabContent } from '../../../pages/metrics/inventory_view/components/node_details/tabs/shared'; +import { TabContent } from '../../../../pages/metrics/inventory_view/components/node_details/tabs/shared'; import { SortBy, useProcessList, ProcessListContextProvider, -} from '../../../pages/metrics/inventory_view/hooks/use_process_list'; -import { getFieldByType } from '../../../../common/inventory_models'; -import type { HostNodeRow } from '../types'; -import type { InventoryItemType } from '../../../../common/inventory_models/types'; +} from '../../../../pages/metrics/inventory_view/hooks/use_process_list'; +import { getFieldByType } from '../../../../../common/inventory_models'; +import type { HostNodeRow } from '../../types'; +import type { InventoryItemType } from '../../../../../common/inventory_models/types'; export interface ProcessesProps { node: HostNodeRow; nodeType: InventoryItemType; currentTime: number; searchFilter?: string; - setSearchFilter?: (searchFilter: { searchFilter: string }) => void; + onSearchFilterChange?: (searchFilter: string) => void; } const options = Object.entries(STATE_NAMES).map(([value, view]: [string, string]) => ({ @@ -49,7 +49,7 @@ export const Processes = ({ node, nodeType, searchFilter, - setSearchFilter, + onSearchFilterChange, }: ProcessesProps) => { const [searchText, setSearchText] = useState(searchFilter ?? ''); const [searchBarState, setSearchBarState] = useState(() => @@ -75,12 +75,12 @@ export const Processes = ({ const debouncedSearchOnChange = useMemo(() => { return debounce<(queryText: string) => void>((queryText) => { - if (setSearchFilter) { - setSearchFilter({ searchFilter: queryText }); + if (onSearchFilterChange) { + onSearchFilterChange(queryText); } setSearchText(queryText); }, 500); - }, [setSearchFilter]); + }, [onSearchFilterChange]); const searchBarOnChange = useCallback( ({ query, queryText }) => { @@ -92,11 +92,11 @@ export const Processes = ({ const clearSearchBar = useCallback(() => { setSearchBarState(Query.MATCH_ALL); - if (setSearchFilter) { - setSearchFilter({ searchFilter: '' }); + if (onSearchFilterChange) { + onSearchFilterChange(''); } setSearchText(''); - }, [setSearchFilter]); + }, [onSearchFilterChange]); return ( diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/processes_table.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx similarity index 96% rename from x-pack/plugins/infra/public/components/asset_details/processes/processes_table.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx index d885cac7cf255e..09111093a226a7 100644 --- a/x-pack/plugins/infra/public/components/asset_details/processes/processes_table.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/processes_table.tsx @@ -25,13 +25,13 @@ import { RIGHT_ALIGNMENT, } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { FORMATTERS } from '../../../../common/formatters'; -import type { SortBy } from '../../../pages/metrics/inventory_view/hooks/use_process_list'; +import { FORMATTERS } from '../../../../../common/formatters'; +import type { SortBy } from '../../../../pages/metrics/inventory_view/hooks/use_process_list'; import type { Process } from './types'; -import { ProcessRow } from '../../../pages/metrics/inventory_view/components/node_details/tabs/processes/process_row'; +import { ProcessRow } from '../../../../pages/metrics/inventory_view/components/node_details/tabs/processes/process_row'; import { StateBadge } from './state_badge'; import { STATE_ORDER } from './states'; -import type { ProcessListAPIResponse } from '../../../../common/http_api'; +import type { ProcessListAPIResponse } from '../../../../../common/http_api'; interface TableProps { processList: ProcessListAPIResponse['processList']; diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/state_badge.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/state_badge.tsx similarity index 100% rename from x-pack/plugins/infra/public/components/asset_details/processes/state_badge.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/state_badge.tsx diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/states.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/states.ts similarity index 100% rename from x-pack/plugins/infra/public/components/asset_details/processes/states.ts rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/states.ts diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/summary_table.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/summary_table.tsx similarity index 97% rename from x-pack/plugins/infra/public/components/asset_details/processes/summary_table.tsx rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/summary_table.tsx index 92007d769a4eb5..6cbf25013f8764 100644 --- a/x-pack/plugins/infra/public/components/asset_details/processes/summary_table.tsx +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/summary_table.tsx @@ -18,7 +18,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import type { ProcessListAPIResponse } from '../../../../common/http_api'; +import type { ProcessListAPIResponse } from '../../../../../common/http_api'; import { STATE_NAMES } from './states'; interface Props { diff --git a/x-pack/plugins/infra/public/components/asset_details/processes/types.ts b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts similarity index 87% rename from x-pack/plugins/infra/public/components/asset_details/processes/types.ts rename to x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts index ef4b177ecae4b7..024ffe9e5cdf5e 100644 --- a/x-pack/plugins/infra/public/components/asset_details/processes/types.ts +++ b/x-pack/plugins/infra/public/components/asset_details/tabs/processes/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { MetricsExplorerSeries } from '../../../../common/http_api'; +import type { MetricsExplorerSeries } from '../../../../../common/http_api'; import { STATE_NAMES } from './states'; export interface Process { diff --git a/x-pack/plugins/infra/public/components/asset_details/tabs_content/tabs_content.tsx b/x-pack/plugins/infra/public/components/asset_details/tabs_content/tabs_content.tsx deleted file mode 100644 index e4c2d028defdf4..00000000000000 --- a/x-pack/plugins/infra/public/components/asset_details/tabs_content/tabs_content.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { type AssetDetailsProps, type TabIds, FlyoutTabIds } from '../asset_details'; -import Metadata from '../metadata/metadata'; -import { Processes } from '../processes/processes'; - -export const AssetDetailsTabContent = ({ - renderedTabsSet, - hostFlyoutOpen, - currentTimeRange, - node, - nodeType, - showActionsColumn, - setHostFlyoutState, - selectedTabId, -}: Pick< - AssetDetailsProps, - | 'renderedTabsSet' - | 'hostFlyoutOpen' - | 'currentTimeRange' - | 'node' - | 'nodeType' - | 'showActionsColumn' - | 'setHostFlyoutState' -> & { selectedTabId: TabIds }) => { - const persistMetadataSearchToUrlState = - setHostFlyoutState && hostFlyoutOpen - ? { - metadataSearchUrlState: hostFlyoutOpen.metadataSearch, - setMetadataSearchUrlState: setHostFlyoutState, - } - : undefined; - - const isTabSelected = (flyoutTabId: FlyoutTabIds) => { - return selectedTabId === flyoutTabId; - }; - - return ( - <> - {renderedTabsSet.current.has(FlyoutTabIds.METADATA) && ( - - )} - {renderedTabsSet.current.has(FlyoutTabIds.PROCESSES) && ( - - )} - - ); -}; diff --git a/x-pack/plugins/infra/public/components/asset_details/types.ts b/x-pack/plugins/infra/public/components/asset_details/types.ts index 8432d6f7085ef5..2fdee6e59a8113 100644 --- a/x-pack/plugins/infra/public/components/asset_details/types.ts +++ b/x-pack/plugins/infra/public/components/asset_details/types.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { InventoryItemType } from '../../../common/inventory_models/types'; import { InfraAssetMetricType } from '../../../common/http_api'; export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider'; @@ -21,3 +22,55 @@ export type HostNodeRow = HostMetadata & HostMetrics & { name: string; }; + +export enum FlyoutTabIds { + METADATA = 'metadata', + PROCESSES = 'processes', +} + +export type TabIds = `${FlyoutTabIds}`; + +export interface TabState { + metadata?: { + query?: string; + showActionsColumn?: boolean; + }; + processes?: { + query?: string; + }; +} + +export interface FlyoutProps { + closeFlyout: () => void; + showInFlyout: true; +} + +export interface FullPageProps { + showInFlyout: false; +} + +export type RenderMode = FlyoutProps | FullPageProps; + +export interface Tab { + id: FlyoutTabIds; + name: string; + 'data-test-subj': string; +} + +export interface AssetDetailsProps { + node: HostNodeRow; + nodeType: InventoryItemType; + currentTimeRange: { + interval: string; + from: number; + to: number; + }; + tabs: Tab[]; + activeTabId?: TabIds; + overrides?: TabState; + renderMode?: RenderMode; + onTabsStateChange?: TabsStateChangeFn; + links?: Array<'uptime' | 'apmServices'>; +} + +export type TabsStateChangeFn = (state: TabState & { activeTabId?: TabIds }) => void; diff --git a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts index d7486479353562..d636cefa049051 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_analysis/api/validate_indices.ts @@ -28,6 +28,7 @@ export const callValidateIndicesAPI = async (requestArgs: RequestArgs, fetch: Ht const response = await fetch(LOG_ANALYSIS_VALIDATE_INDICES_PATH, { method: 'POST', body: JSON.stringify( + // @ts-expect-error TODO: fix after elasticsearch-js bump validationIndicesRequestPayloadRT.encode({ data: { indices, fields, runtimeMappings } }) ), }); diff --git a/x-pack/plugins/infra/public/locators/discover_logs_locator.ts b/x-pack/plugins/infra/public/locators/discover_logs_locator.ts index 8693c5e281078f..9e610d41af34b8 100644 --- a/x-pack/plugins/infra/public/locators/discover_logs_locator.ts +++ b/x-pack/plugins/infra/public/locators/discover_logs_locator.ts @@ -7,13 +7,12 @@ import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import type { LogsLocatorDependencies, LogsLocatorParams } from './logs_locator'; - -const DISCOVER_LOGS_LOCATOR_ID = 'DISCOVER_LOGS_LOCATOR'; +import { LOGS_LOCATOR_ID } from './logs_locator'; export type DiscoverLogsLocator = LocatorPublic; export class DiscoverLogsLocatorDefinition implements LocatorDefinition { - public readonly id = DISCOVER_LOGS_LOCATOR_ID; + public readonly id = LOGS_LOCATOR_ID; constructor(protected readonly deps: LogsLocatorDependencies) {} diff --git a/x-pack/plugins/infra/public/locators/discover_node_logs_locator.ts b/x-pack/plugins/infra/public/locators/discover_node_logs_locator.ts index 727c7abb401a5c..e4ad2b34c5e669 100644 --- a/x-pack/plugins/infra/public/locators/discover_node_logs_locator.ts +++ b/x-pack/plugins/infra/public/locators/discover_node_logs_locator.ts @@ -7,13 +7,12 @@ import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import type { NodeLogsLocatorDependencies, NodeLogsLocatorParams } from './node_logs_locator'; - -const DISCOVER_NODE_LOGS_LOCATOR_ID = 'DISCOVER_NODE_LOGS_LOCATOR'; +import { NODE_LOGS_LOCATOR_ID } from './node_logs_locator'; export type DiscoverNodeLogsLocator = LocatorPublic; export class DiscoverNodeLogsLocatorDefinition implements LocatorDefinition { - public readonly id = DISCOVER_NODE_LOGS_LOCATOR_ID; + public readonly id = NODE_LOGS_LOCATOR_ID; constructor(protected readonly deps: NodeLogsLocatorDependencies) {} diff --git a/x-pack/plugins/infra/public/locators/logs_locator.ts b/x-pack/plugins/infra/public/locators/logs_locator.ts index fd78b9cce74fea..3c95384ff9af75 100644 --- a/x-pack/plugins/infra/public/locators/logs_locator.ts +++ b/x-pack/plugins/infra/public/locators/logs_locator.ts @@ -11,7 +11,7 @@ import type { LogViewReference } from '../../common/log_views'; import type { TimeRange } from '../../common/time'; import type { InfraClientCoreSetup } from '../types'; -const LOGS_LOCATOR_ID = 'LOGS_LOCATOR'; +export const LOGS_LOCATOR_ID = 'LOGS_LOCATOR'; export interface LogsLocatorParams extends SerializableRecord { /** Defines log position */ diff --git a/x-pack/plugins/infra/public/locators/node_logs_locator.ts b/x-pack/plugins/infra/public/locators/node_logs_locator.ts index afa49882262c4d..f0c9ad0ff73ca5 100644 --- a/x-pack/plugins/infra/public/locators/node_logs_locator.ts +++ b/x-pack/plugins/infra/public/locators/node_logs_locator.ts @@ -9,7 +9,7 @@ import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import type { InventoryItemType } from '../../common/inventory_models/types'; import type { LogsLocatorDependencies, LogsLocatorParams } from './logs_locator'; -const NODE_LOGS_LOCATOR_ID = 'NODE_LOGS_LOCATOR'; +export const NODE_LOGS_LOCATOR_ID = 'NODE_LOGS_LOCATOR'; export interface NodeLogsLocatorParams extends LogsLocatorParams { nodeId: string; diff --git a/x-pack/plugins/infra/public/pages/link_to/index.ts b/x-pack/plugins/infra/public/pages/link_to/index.ts index d2a10b0c4504f8..0991c6dba1936c 100644 --- a/x-pack/plugins/infra/public/pages/link_to/index.ts +++ b/x-pack/plugins/infra/public/pages/link_to/index.ts @@ -7,5 +7,5 @@ export { LinkToLogsPage } from './link_to_logs'; export { LinkToMetricsPage } from './link_to_metrics'; -export { getNodeLogsUrl, RedirectToNodeLogs } from './redirect_to_node_logs'; +export { RedirectToNodeLogs } from './redirect_to_node_logs'; export { getNodeDetailUrl, RedirectToNodeDetail } from './redirect_to_node_detail'; diff --git a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index ab5723427a1ecf..7cf40df09b80ea 100644 --- a/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { LinkDescriptor } from '@kbn/observability-shared-plugin/public'; import { useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { InventoryItemType } from '../../../common/inventory_models/types'; @@ -47,23 +46,3 @@ export const RedirectToNodeLogs = ({ return null; }; - -export const getNodeLogsUrl = ({ - nodeId, - nodeType, - time, -}: { - nodeId: string; - nodeType: InventoryItemType; - time?: number; -}): LinkDescriptor => { - return { - app: 'logs', - pathname: `link-to/${nodeType}-logs/${nodeId}`, - search: time - ? { - time: `${time}`, - } - : undefined, - }; -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx index fda3185b4c3638..7686330566d6a2 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/flyout_wrapper.tsx @@ -8,10 +8,8 @@ import React, { useMemo } from 'react'; import type { InventoryItemType } from '../../../../../../common/inventory_models/types'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; -import { useLazyRef } from '../../../../../hooks/use_lazy_ref'; import type { HostNodeRow } from '../../hooks/use_hosts_table'; -import type { Tab } from '../../../../../components/asset_details/asset_details'; -import { useHostFlyoutOpen } from '../../hooks/use_host_flyout_open_url_state'; +import { HostFlyout, useHostFlyoutUrlState } from '../../hooks/use_host_flyout_url_state'; import { AssetDetails } from '../../../../../components/asset_details/asset_details'; import { metadataTab, processesTab } from './tabs'; @@ -32,31 +30,36 @@ export const FlyoutWrapper = ({ node, closeFlyout }: Props) => { [getDateRangeAsTimestamp] ); - const [hostFlyoutOpen, setHostFlyoutOpen] = useHostFlyoutOpen(); - - // This map allow to keep track of which tabs content have been rendered the first time. - // We need it in order to load a tab content only if it gets clicked, and then keep it in the DOM for performance improvement. - const renderedTabsSet = useLazyRef(() => new Set([hostFlyoutOpen?.selectedTabId])); - - const onTabClick = (tab: Tab) => { - renderedTabsSet.current.add(tab.id); // On a tab click, mark the tab content as allowed to be rendered - setHostFlyoutOpen({ selectedTabId: tab.id }); - }; + const [hostFlyoutOpen, setHostFlyoutOpen] = useHostFlyoutUrlState(); return ( + setHostFlyoutOpen({ + metadataSearch: state.metadata?.query, + processSearch: state.processes?.query, + selectedTabId: state.activeTabId as HostFlyout['selectedTabId'], + }) + } tabs={[metadataTab, processesTab]} links={['apmServices', 'uptime']} - nodeType={NODE_TYPE} + renderMode={{ + showInFlyout: true, + closeFlyout, + }} /> ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts index 8485664a4cd6ad..43ada7b6ae4bf2 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/host_details_flyout/tabs.ts @@ -6,8 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import type { Tab } from '../../../../../components/asset_details/asset_details'; -import { FlyoutTabIds } from '../../hooks/use_host_flyout_open_url_state'; +import { FlyoutTabIds, type Tab } from '../../../../../components/asset_details/types'; export const processesTab: Tab = { id: FlyoutTabIds.PROCESSES, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_open_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_open_url_state.ts deleted file mode 100644 index 1db5787719a746..00000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_open_url_state.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as rt from 'io-ts'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { constant, identity } from 'fp-ts/lib/function'; -import { useUrlState } from '../../../../utils/use_url_state'; - -export enum FlyoutTabIds { - METADATA = 'metadata', - PROCESSES = 'processes', -} - -export const GET_DEFAULT_TABLE_PROPERTIES = { - clickedItemId: '', - selectedTabId: FlyoutTabIds.METADATA, - searchFilter: '', - metadataSearch: '', -}; -const HOST_TABLE_PROPERTIES_URL_STATE_KEY = 'hostFlyoutOpen'; - -type Action = rt.TypeOf; -export type SetNewHostFlyoutOpen = (newProp: Action) => void; -type SetNewHostFlyoutClose = () => void; - -export const useHostFlyoutOpen = (): [ - HostFlyoutOpen, - SetNewHostFlyoutOpen, - SetNewHostFlyoutClose -] => { - const [urlState, setUrlState] = useUrlState({ - defaultState: '', - decodeUrlState, - encodeUrlState, - urlStateKey: HOST_TABLE_PROPERTIES_URL_STATE_KEY, - }); - - const setHostFlyoutOpen = (newProps: Action) => - typeof urlState !== 'string' - ? setUrlState({ ...urlState, ...newProps }) - : setUrlState({ ...GET_DEFAULT_TABLE_PROPERTIES, ...newProps }); - - const setFlyoutClosed = () => setUrlState(''); - - return [urlState as HostFlyoutOpen, setHostFlyoutOpen, setFlyoutClosed]; -}; - -const FlyoutTabIdRT = rt.union([rt.literal('metadata'), rt.literal('processes')]); -const ClickedItemIdRT = rt.string; -const SearchFilterRT = rt.string; - -const SetFlyoutTabId = rt.partial({ - selectedTabId: FlyoutTabIdRT, -}); - -const SetClickedItemIdRT = rt.partial({ - clickedItemId: ClickedItemIdRT, -}); - -const SetSearchFilterRT = rt.partial({ - searchFilter: SearchFilterRT, -}); - -const SetMetadataSearchRT = rt.partial({ - metadataSearch: SearchFilterRT, -}); - -const ActionRT = rt.intersection([ - SetClickedItemIdRT, - SetFlyoutTabId, - SetSearchFilterRT, - SetMetadataSearchRT, -]); - -const HostFlyoutOpenRT = rt.type({ - clickedItemId: ClickedItemIdRT, - selectedTabId: FlyoutTabIdRT, - searchFilter: SearchFilterRT, - metadataSearch: SearchFilterRT, -}); - -const HostFlyoutUrlRT = rt.union([HostFlyoutOpenRT, rt.string]); - -type HostFlyoutUrl = rt.TypeOf; -type HostFlyoutOpen = rt.TypeOf; - -const encodeUrlState = HostFlyoutUrlRT.encode; -const decodeUrlState = (value: unknown) => { - return pipe(HostFlyoutUrlRT.decode(value), fold(constant('undefined'), identity)); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_url_state.ts new file mode 100644 index 00000000000000..e54a48a9432f33 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_flyout_url_state.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import isEmpty from 'lodash/isEmpty'; +import omitBy from 'lodash/omitBy'; +import { FlyoutTabIds } from '../../../../components/asset_details/types'; +import { useUrlState } from '../../../../utils/use_url_state'; + +export const DEFAULT_STATE: HostFlyout = { + clickedItemId: '', + selectedTabId: FlyoutTabIds.METADATA, + processSearch: undefined, + metadataSearch: undefined, +}; +const HOST_FLYOUT_URL_STATE_KEY = 'hostFlyoutOpen'; + +type SetHostFlyoutState = (newProp: Payload | null) => void; + +export const useHostFlyoutUrlState = (): [HostFlyoutUrl, SetHostFlyoutState] => { + const [urlState, setUrlState] = useUrlState({ + defaultState: null, + decodeUrlState, + encodeUrlState, + urlStateKey: HOST_FLYOUT_URL_STATE_KEY, + }); + + const setHostFlyoutState = (newProps: Payload | null) => { + if (!newProps) { + setUrlState(DEFAULT_STATE); + } else { + const payload = omitBy(newProps, isEmpty); + setUrlState({ ...(urlState ?? DEFAULT_STATE), ...payload }); + } + }; + + return [urlState as HostFlyoutUrl, setHostFlyoutState]; +}; + +const FlyoutTabIdRT = rt.union([ + rt.literal(FlyoutTabIds.METADATA), + rt.literal(FlyoutTabIds.PROCESSES), +]); + +const HostFlyoutStateRT = rt.intersection([ + rt.type({ + clickedItemId: rt.string, + selectedTabId: FlyoutTabIdRT, + }), + rt.partial({ + processSearch: rt.string, + metadataSearch: rt.string, + }), +]); + +const HostFlyoutUrlRT = rt.union([HostFlyoutStateRT, rt.null]); + +type HostFlyoutState = rt.TypeOf; +type HostFlyoutUrl = rt.TypeOf; +type Payload = Partial; +export type HostFlyout = rt.TypeOf; + +const encodeUrlState = HostFlyoutUrlRT.encode; +const decodeUrlState = (value: unknown) => { + return pipe(HostFlyoutUrlRT.decode(value), fold(constant(undefined), identity)); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index 6fb206880cf44f..ae2796d9d7b277 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -20,7 +20,7 @@ import { InfraAssetMetricsItem, InfraAssetMetricType, } from '../../../../../common/http_api'; -import { useHostFlyoutOpen } from './use_host_flyout_open_url_state'; +import { useHostFlyoutUrlState } from './use_host_flyout_url_state'; import { Sorting, useHostsTableUrlState } from './use_hosts_table_url_state'; import { useHostsViewContext } from './use_hosts_view'; import { useUnifiedSearchContext } from './use_unified_search'; @@ -171,9 +171,9 @@ export const useHostsTable = () => { services: { telemetry }, } = useKibanaContextForPlugin(); - const [hostFlyoutOpen, setHostFlyoutOpen, setFlyoutClosed] = useHostFlyoutOpen(); + const [hostFlyoutState, setHostFlyoutState] = useHostFlyoutUrlState(); - const closeFlyout = () => setFlyoutClosed(); + const closeFlyout = useCallback(() => setHostFlyoutState(null), [setHostFlyoutState]); const reportHostEntryClick = useCallback( ({ name, cloudProvider }: HostNodeRow['title']) => { @@ -204,8 +204,8 @@ export const useHostsTable = () => { const items = useMemo(() => buildItemsList(hostNodes), [hostNodes]); const clickedItem = useMemo( - () => items.find(({ id }) => id === hostFlyoutOpen.clickedItemId), - [hostFlyoutOpen.clickedItemId, items] + () => items.find(({ id }) => id === hostFlyoutState?.clickedItemId), + [hostFlyoutState?.clickedItemId, items] ); const currentPage = useMemo(() => { @@ -228,19 +228,19 @@ export const useHostsTable = () => { name: toggleDialogActionLabel, description: toggleDialogActionLabel, icon: ({ id }) => - hostFlyoutOpen.clickedItemId && id === hostFlyoutOpen.clickedItemId + hostFlyoutState?.clickedItemId && id === hostFlyoutState?.clickedItemId ? 'minimize' : 'expand', type: 'icon', 'data-test-subj': 'hostsView-flyout-button', onClick: ({ id }) => { - setHostFlyoutOpen({ + setHostFlyoutState({ clickedItemId: id, }); - if (id === hostFlyoutOpen.clickedItemId) { - setFlyoutClosed(); + if (id === hostFlyoutState?.clickedItemId) { + setHostFlyoutState(null); } else { - setHostFlyoutOpen({ clickedItemId: id }); + setHostFlyoutState({ clickedItemId: id }); } }, }, @@ -317,11 +317,10 @@ export const useHostsTable = () => { }, ], [ - hostFlyoutOpen.clickedItemId, + hostFlyoutState?.clickedItemId, reportHostEntryClick, searchCriteria.dateRange, - setFlyoutClosed, - setHostFlyoutOpen, + setHostFlyoutState, ] ); @@ -331,7 +330,7 @@ export const useHostsTable = () => { currentPage, closeFlyout, items, - isFlyoutOpen: !!hostFlyoutOpen.clickedItemId, + isFlyoutOpen: !!hostFlyoutState?.clickedItemId, onTableChange, pagination, sorting, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx index 773a1261e81e23..3024ff3d1bd067 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/logs.tsx @@ -13,14 +13,16 @@ import { EuiFieldSearch } from '@elastic/eui'; import { EuiFlexGroup } from '@elastic/eui'; import { EuiFlexItem } from '@elastic/eui'; import { EuiButtonEmpty } from '@elastic/eui'; -import { useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; import { TabContent, TabProps } from './shared'; import { LogStream } from '../../../../../../components/log_stream'; import { useWaffleOptionsContext } from '../../../hooks/use_waffle_options'; import { findInventoryFields } from '../../../../../../../common/inventory_models'; -import { getNodeLogsUrl } from '../../../../../link_to'; const TabComponent = (props: TabProps) => { + const { services } = useKibanaContextForPlugin(); + const { locators } = services; const [textQuery, setTextQuery] = useState(''); const [textQueryDebounced, setTextQueryDebounced] = useState(''); const endTimestamp = props.currentTime; @@ -46,13 +48,14 @@ const TabComponent = (props: TabProps) => { setTextQuery(e.target.value); }, []); - const nodeLogsMenuItemLinkProps = useLinkProps( - getNodeLogsUrl({ + const logsUrl = useMemo(() => { + return locators.nodeLogsLocator.getRedirectUrl({ nodeType, nodeId: node.id, time: startTimestamp, - }) - ); + filter: textQueryDebounced, + }); + }, [locators.nodeLogsLocator, node.id, nodeType, startTimestamp, textQueryDebounced]); return ( @@ -70,18 +73,20 @@ const TabComponent = (props: TabProps) => { /> - - - + + + + + = withTheme const [flyoutVisible, setFlyoutVisible] = useState(false); const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; - const { application, share } = useKibana() - .services; + const { services } = useKibanaContextForPlugin(); + const { application, share, locators } = services; const uiCapabilities = application?.capabilities; // Due to the changing nature of the fields between APM and this UI, // We need to have some exceptions until 7.0 & ECS is finalized. Reference @@ -76,13 +75,6 @@ export const NodeContextMenu: React.FC = withTheme return { label: '', value: '' }; }, [nodeType, node.ip, node.id]); - const nodeLogsMenuItemLinkProps = useLinkProps( - getNodeLogsUrl({ - nodeType, - nodeId: node.id, - time: currentTime, - }) - ); const nodeDetailMenuItemLinkProps = useLinkProps({ ...getNodeDetailUrl({ nodeType, @@ -104,7 +96,11 @@ export const NodeContextMenu: React.FC = withTheme defaultMessage: '{inventoryName} logs', values: { inventoryName: inventoryModel.singularDisplayName }, }), - ...nodeLogsMenuItemLinkProps, + href: locators.nodeLogsLocator.getRedirectUrl({ + nodeType, + nodeId: node.id, + time: currentTime, + }), 'data-test-subj': 'viewLogsContextMenuItem', isDisabled: !showLogsLink, }; diff --git a/x-pack/plugins/infra/server/lib/sources/index.ts b/x-pack/plugins/infra/server/lib/sources/index.ts index 73b50ac2662cce..1afcd7415daf6b 100644 --- a/x-pack/plugins/infra/server/lib/sources/index.ts +++ b/x-pack/plugins/infra/server/lib/sources/index.ts @@ -12,3 +12,4 @@ export { } from './saved_object_type'; export * from './sources'; export * from '../../../common/source_configuration/source_configuration'; +export { SourceConfigurationSavedObjectRT } from './types'; diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts index c1042f3794f0c2..693c4aba20ae1a 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.ts @@ -22,8 +22,8 @@ import { InfraStaticSourceConfiguration, SourceConfigurationConfigFileProperties, sourceConfigurationConfigFilePropertiesRT, - SourceConfigurationSavedObjectRuntimeType, } from '../../../common/source_configuration/source_configuration'; +import { SourceConfigurationSavedObjectRT } from '.'; import { InfraConfig } from '../..'; import { defaultSourceConfiguration } from './defaults'; import { AnomalyThresholdRangeError, NotFoundError } from './errors'; @@ -258,7 +258,7 @@ export const mergeSourceConfiguration = ( export const convertSavedObjectToSavedSourceConfiguration = (savedObject: SavedObject) => pipe( - SourceConfigurationSavedObjectRuntimeType.decode(savedObject), + SourceConfigurationSavedObjectRT.decode(savedObject), map((savedSourceConfiguration) => ({ id: savedSourceConfiguration.id, version: savedSourceConfiguration.version, diff --git a/x-pack/plugins/infra/server/lib/sources/types.ts b/x-pack/plugins/infra/server/lib/sources/types.ts new file mode 100644 index 00000000000000..7c2448dc48018b --- /dev/null +++ b/x-pack/plugins/infra/server/lib/sources/types.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import moment from 'moment'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { chain } from 'fp-ts/lib/Either'; + +export const TimestampFromString = new rt.Type( + 'TimestampFromString', + (input): input is number => typeof input === 'number', + (input, context) => + pipe( + rt.string.validate(input, context), + chain((stringInput) => { + const momentValue = moment(stringInput); + return momentValue.isValid() + ? rt.success(momentValue.valueOf()) + : rt.failure(stringInput, context); + }) + ), + (output) => new Date(output).toISOString() +); + +export const SourceConfigurationSavedObjectFieldColumnRT = rt.type({ + fieldColumn: rt.type({ + id: rt.string, + field: rt.string, + }), +}); + +export const SourceConfigurationSavedObjectMessageColumnRT = rt.type({ + messageColumn: rt.type({ + id: rt.string, + }), +}); + +export const SourceConfigurationSavedObjectTimestampColumnRT = rt.type({ + timestampColumn: rt.type({ + id: rt.string, + }), +}); + +export const SourceConfigurationSavedObjectColumnRT = rt.union([ + SourceConfigurationSavedObjectTimestampColumnRT, + SourceConfigurationSavedObjectMessageColumnRT, + SourceConfigurationSavedObjectFieldColumnRT, +]); + +export const logIndexPatternSavedObjectReferenceRT = rt.type({ + type: rt.literal('index_pattern'), + indexPatternId: rt.string, +}); + +export const logIndexNameSavedObjectReferenceRT = rt.type({ + type: rt.literal('index_name'), + indexName: rt.string, +}); + +export const logIndexSavedObjectReferenceRT = rt.union([ + logIndexPatternSavedObjectReferenceRT, + logIndexNameSavedObjectReferenceRT, +]); + +export const SourceConfigurationSavedObjectAttributesRT = rt.type({ + name: rt.string, + description: rt.string, + metricAlias: rt.string, + logIndices: logIndexSavedObjectReferenceRT, + inventoryDefaultView: rt.string, + metricsExplorerDefaultView: rt.string, + fields: rt.record(rt.string, rt.unknown), + logColumns: rt.array(SourceConfigurationSavedObjectColumnRT), + anomalyThreshold: rt.number, +}); + +export const SavedSourceConfigurationSavedObjectRT = rt.partial( + SourceConfigurationSavedObjectAttributesRT.props +); + +export const SourceConfigurationSavedObjectRT = rt.intersection([ + rt.type({ + id: rt.string, + attributes: SavedSourceConfigurationSavedObjectRT, + }), + rt.partial({ + version: rt.string, + updated_at: TimestampFromString, + }), +]); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 175ef4eb37179e..29b282b5fbf209 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -40,7 +40,6 @@ export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) = const { has_all_requested: hasAllPrivileges, cluster } = await clusterClient.asCurrentUser.security.hasPrivileges({ - // @ts-expect-error @elastic/elasticsearch SecurityClusterPrivilege doesn’t contain all the priviledges body: { cluster: APP_CLUSTER_REQUIRED_PRIVILEGES }, }); diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 8c843e67cea874..f99b3bf1719b41 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -23,17 +23,16 @@ export const INITIAL_LAYERS_KEY = 'initialLayers'; export const MAPS_APP_PATH = `app/${APP_ID}`; export const MAP_PATH = 'map'; -export const GIS_API_PATH = `api/${APP_ID}`; -export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`; -export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`; -export const INDEX_SOURCE_API_PATH = `${GIS_API_PATH}/docSource`; -export const API_ROOT_PATH = `/${GIS_API_PATH}`; -export const INDEX_FEATURE_PATH = `/${GIS_API_PATH}/feature`; -export const GET_MATCHING_INDEXES_PATH = `/${GIS_API_PATH}/getMatchingIndexes`; -export const CHECK_IS_DRAWING_INDEX = `/${GIS_API_PATH}/checkIsDrawingIndex`; - -export const MVT_GETTILE_API_PATH = 'mvt/getTile'; -export const MVT_GETGRIDTILE_API_PATH = 'mvt/getGridTile'; +export const GIS_INTERNAL_PATH = `internal/${APP_ID}`; +export const INDEX_SETTINGS_API_PATH = `${GIS_INTERNAL_PATH}/indexSettings`; +export const FONTS_API_PATH = `${GIS_INTERNAL_PATH}/fonts`; +export const INDEX_SOURCE_API_PATH = `${GIS_INTERNAL_PATH}/docSource`; +export const INDEX_FEATURE_PATH = `/${GIS_INTERNAL_PATH}/feature`; +export const GET_MATCHING_INDEXES_PATH = `/${GIS_INTERNAL_PATH}/getMatchingIndexes`; +export const CHECK_IS_DRAWING_INDEX = `/${GIS_INTERNAL_PATH}/checkIsDrawingIndex`; +export const MVT_GETTILE_API_PATH = `/${GIS_INTERNAL_PATH}/mvt/getTile`; +export const MVT_GETGRIDTILE_API_PATH = `/${GIS_INTERNAL_PATH}/mvt/getGridTile`; + export const OPEN_LAYER_WIZARD = 'openLayerWizard'; // Identifies centroid feature. diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index d9b7cc4540ec27..592ad8cd4fbef6 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -1051,8 +1051,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer { async addFeature(geometry: Geometry | Position[]) { const layerSource = this.getSource(); - const defaultFields = await layerSource.getDefaultFields(); - await layerSource.addFeature(geometry, defaultFields); + await layerSource.addFeature(geometry); } async deleteFeature(featureId: string) { diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/new_vector_layer_wizard/create_new_index_pattern.ts b/x-pack/plugins/maps/public/classes/layers/wizards/new_vector_layer_wizard/create_new_index_pattern.ts index 90ef4b90a49923..90e0bceec7ef69 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/new_vector_layer_wizard/create_new_index_pattern.ts +++ b/x-pack/plugins/maps/public/classes/layers/wizards/new_vector_layer_wizard/create_new_index_pattern.ts @@ -19,6 +19,7 @@ export const createNewIndexAndPattern = async ({ return await getHttp().fetch({ path: `/${INDEX_SOURCE_API_PATH}`, method: 'POST', + version: '1', body: JSON.stringify({ index: indexName, mappings: { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index 71614aebe541eb..7e23353460df6e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -313,7 +313,7 @@ describe('ESGeoGridSource', () => { const tileUrl = await mvtGeogridSource.getTileUrl(vectorSourceRequestMeta, '1234', false, 5); const urlParts = tileUrl.split('?'); - expect(urlParts[0]).toEqual('rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf'); + expect(urlParts[0]).toEqual('rootdir/internal/maps/mvt/getGridTile/{z}/{x}/{y}.pbf'); const params = new URLSearchParams(urlParts[1]); expect(Object.fromEntries(params)).toEqual({ diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index ada40e859d181d..25e447d6ad3ea7 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -25,7 +25,6 @@ import { ES_GEO_FIELD_TYPE, GEOCENTROID_AGG_NAME, GEOTILE_GRID_AGG_NAME, - GIS_API_PATH, GRID_RESOLUTION, MVT_GETGRIDTILE_API_PATH, RENDER_AS, @@ -574,7 +573,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo searchSource.setField('aggs', this.getValueAggsDsl(dataView)); const mvtUrlServicePath = getHttp().basePath.prepend( - `/${GIS_API_PATH}/${MVT_GETGRIDTILE_API_PATH}/{z}/{x}/{y}.pbf` + `${MVT_GETGRIDTILE_API_PATH}/{z}/{x}/{y}.pbf` ); const tileUrlParams = getTileUrlParams({ diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index a6adb7673e00a6..7f7fde393de91d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -122,7 +122,7 @@ describe('ESSearchSource', () => { const tileUrl = await esSearchSource.getTileUrl(requestMeta, '1234', false, 5); const urlParts = tileUrl.split('?'); - expect(urlParts[0]).toEqual('rootdir/api/maps/mvt/getTile/{z}/{x}/{y}.pbf'); + expect(urlParts[0]).toEqual('rootdir/internal/maps/mvt/getTile/{z}/{x}/{y}.pbf'); const params = new URLSearchParams(urlParts[1]); expect(Object.fromEntries(params)).toEqual({ diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index 2db15d3f0cd722..6bcc061d44040f 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -37,7 +37,6 @@ import { ES_GEO_FIELD_TYPE, FIELD_ORIGIN, GEO_JSON_TYPE, - GIS_API_PATH, MVT_GETTILE_API_PATH, SCALING_TYPES, SOURCE_TYPES, @@ -491,7 +490,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField); } - async getSourceIndexList(): Promise { + async _getSourceIndexList(): Promise { const dataView = await this.getIndexPattern(); try { const { success, matchingIndexes } = await getMatchingIndexes(dataView.getIndexPattern()); @@ -503,12 +502,12 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } async supportsFeatureEditing(): Promise { - const matchingIndexes = await this.getSourceIndexList(); + const matchingIndexes = await this._getSourceIndexList(); // For now we only support 1:1 index-pattern:index matches return matchingIndexes.length === 1; } - async getDefaultFields(): Promise>> { + async _getNewFeatureFields(): Promise>> { if (!(await this._isDrawingIndex())) { return {}; } @@ -820,7 +819,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } async _getEditableIndex(): Promise { - const indexList = await this.getSourceIndexList(); + const indexList = await this._getSourceIndexList(); if (indexList.length === 0) { throw new Error( i18n.translate('xpack.maps.source.esSearch.indexZeroLengthEditError', { @@ -838,12 +837,14 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return indexList[0]; } - async addFeature( - geometry: Geometry | Position[], - defaultFields: Record> - ) { + async addFeature(geometry: Geometry | Position[]) { const index = await this._getEditableIndex(); - await addFeatureToIndex(index, geometry, this.getGeoFieldName(), defaultFields); + await addFeatureToIndex( + index, + geometry, + this.getGeoFieldName(), + await this._getNewFeatureFields() + ); } async deleteFeature(featureId: string) { @@ -894,9 +895,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource }) ); - const mvtUrlServicePath = getHttp().basePath.prepend( - `/${GIS_API_PATH}/${MVT_GETTILE_API_PATH}/{z}/{x}/{y}.pbf` - ); + const mvtUrlServicePath = getHttp().basePath.prepend(`${MVT_GETTILE_API_PATH}/{z}/{x}/{y}.pbf`); const tileUrlParams = getTileUrlParams({ geometryFieldName: this._descriptor.geoField, diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts index 4619cc32ae617a..a2304daf404971 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts @@ -24,6 +24,7 @@ export const addFeatureToIndex = async ( return await getHttp().fetch({ path: `${INDEX_FEATURE_PATH}`, method: 'POST', + version: '1', body: JSON.stringify({ index: indexName, data, @@ -35,6 +36,7 @@ export const deleteFeatureFromIndex = async (indexName: string, featureId: strin return await getHttp().fetch({ path: `${INDEX_FEATURE_PATH}/${featureId}`, method: 'DELETE', + version: '1', body: JSON.stringify({ index: indexName, }), @@ -48,6 +50,7 @@ export const getMatchingIndexes = async (indexPattern: string) => { }>({ path: GET_MATCHING_INDEXES_PATH, method: 'GET', + version: '1', query: { indexPattern }, }); }; @@ -59,6 +62,7 @@ export const getIsDrawLayer = async (index: string) => { }>({ path: CHECK_IS_DRAWING_INDEX, method: 'GET', + version: '1', query: { index }, }); }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/load_index_settings.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/load_index_settings.ts index a736fc664a155c..f9d68053246d12 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/load_index_settings.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/load_index_settings.ts @@ -38,6 +38,7 @@ async function fetchIndexSettings(indexPatternTitle: string): Promise>> { - return {}; - } - supportsJoins(): boolean { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index f17de87cbef334..103e7735025c58 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -122,11 +122,7 @@ export interface IVectorSource extends ISource { getSourceStatus(sourceDataRequest?: DataRequest): SourceStatus; getTimesliceMaskFieldName(): Promise; supportsFeatureEditing(): Promise; - getDefaultFields(): Promise>>; - addFeature( - geometry: Geometry | Position[], - defaultFields: Record> - ): Promise; + addFeature(geometry: Geometry | Position[]): Promise; deleteFeature(featureId: string): Promise; /* @@ -242,10 +238,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return null; } - async addFeature( - geometry: Geometry | Position[], - defaultFields: Record> - ) { + async addFeature(geometry: Geometry | Position[]) { throw new Error('Should implement VectorSource#addFeature'); } @@ -257,10 +250,6 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return false; } - async getDefaultFields(): Promise>> { - return {}; - } - getFeatureActions({ addFilters, geoFieldNames, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/__snapshots__/marker_size_legend.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/__snapshots__/marker_size_legend.test.tsx.snap new file mode 100644 index 00000000000000..984d316d0fec2c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/__snapshots__/marker_size_legend.test.tsx.snap @@ -0,0 +1,958 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should invert legend 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + + + + + + + + + + + +
+`; + +exports[`Should render legend 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+`; + +exports[`Should render legend with 2 markers when size difference does not provide enough vertical space for more labels 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + + + + + + +
+`; + +exports[`Should render legend with 3 markers when size difference does not provide enough vertical space for more labels 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + + + + + + + + + + + +
+`; + +exports[`Should render legend with only max marker when size difference does not provide enough vertical space for more labels 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + +
+`; + +exports[`Should render legend without label cutoff when min size is 1 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + + + + + + +
+`; + +exports[`Should render max label with std clamp notification 1`] = ` +
+ + + + + + + bytes + + + + + + + + + + + + + +
+`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/__snapshots__/ordinal_legend.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/__snapshots__/ordinal_legend.test.tsx.snap new file mode 100644 index 00000000000000..0a4d629cab86f1 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/__snapshots__/ordinal_legend.test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render legend 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + } + invert={false} + maxLabel="19KB" + minLabel="0KB" + propertyLabel="Border width" +/> +`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/get_ordinal_label.ts b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/get_ordinal_label.ts new file mode 100644 index 00000000000000..bcae9d5543341e --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/get_ordinal_label.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function getMaxLabel( + isFieldMetaEnabled: boolean, + isMaxOutsideStdRange: boolean, + max: number | string +) { + return isFieldMetaEnabled && isMaxOutsideStdRange ? `> ${max}` : max; +} + +export function getMinLabel( + isFieldMetaEnabled: boolean, + isMinOutsideStdRange: boolean, + min: number | string +) { + return isFieldMetaEnabled && isMinOutsideStdRange ? `< ${min}` : min; +} diff --git a/x-pack/test/security_solution_cypress/ftr_provider_context.d.ts b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/index.ts similarity index 58% rename from x-pack/test/security_solution_cypress/ftr_provider_context.d.ts rename to x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/index.ts index aa56557c09df82..6ca10563be5a05 100644 --- a/x-pack/test/security_solution_cypress/ftr_provider_context.d.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/index.ts @@ -5,8 +5,5 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; - -import { services } from './services'; - -export type FtrProviderContext = GenericFtrProviderContext; +export { MarkerSizeLegend } from './marker_size_legend'; +export { OrdinalLegend } from './ordinal_legend'; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_list.ts b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_list.ts new file mode 100644 index 00000000000000..67ea90a76465e6 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_list.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ReactNode } from 'react'; + +export interface Marker { + svg: ReactNode; + textY: number; +} + +export class MarkerList { + private readonly _minFontDistance; + private readonly _maxMarker; + private readonly _markers: Marker[] = []; + + constructor(fontSize: number, maxMarker: Marker) { + this._minFontDistance = fontSize * 0.85; + this._maxMarker = maxMarker; + } + + push(marker: Marker) { + if (marker.textY - this._maxMarker.textY < this._minFontDistance) { + return; + } + + if (this._markers.length === 0) { + this._markers.push(marker); + return; + } + + // only push marker when there is enough vertical space to display text without collisions + const prevMarker = this._markers[this._markers.length - 1]; + if (prevMarker.textY - marker.textY > this._minFontDistance) { + this._markers.push(marker); + } + } + + getMarkers() { + const svgs = this._markers.map((marker: Marker) => { + return marker.svg; + }); + return [...svgs, this._maxMarker.svg]; + } +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_size_legend.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_size_legend.test.tsx new file mode 100644 index 00000000000000..89c442729efe97 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_size_legend.test.tsx @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { FIELD_ORIGIN } from '../../../../../../../common/constants'; +import type { DynamicSizeProperty } from '../../../properties/dynamic_size_property'; +import type { IField } from '../../../../../fields/field'; +import { MarkerSizeLegend } from './marker_size_legend'; + +const dynamicSizeOptions = { + minSize: 7, + maxSize: 32, + field: { + name: 'bytes', + origin: FIELD_ORIGIN.SOURCE, + }, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, + invert: false, +}; + +const mockStyle = { + formatField: (value: number) => { + return `${value * 0.001}KB`; + }, + getDisplayStyleName: () => { + return 'Symbol size'; + }, + getField: () => { + return { + getLabel: () => { + return 'bytes'; + }, + } as unknown as IField; + }, + getOptions: () => { + return dynamicSizeOptions; + }, + getRangeFieldMeta: () => { + return { + min: 0, + max: 19000, + delta: 19000, + }; + }, + isFieldMetaEnabled: () => { + return true; + }, +} as unknown as DynamicSizeProperty; + +test('Should render legend', async () => { + const component = shallow(); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); + +test('Should render legend with 3 markers when size difference does not provide enough vertical space for more labels', async () => { + const component = shallow( + { + return { + ...dynamicSizeOptions, + maxSize: 24, + }; + }, + } as unknown as DynamicSizeProperty + } + /> + ); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); + +test('Should render legend with 2 markers when size difference does not provide enough vertical space for more labels', async () => { + const component = shallow( + { + return { + ...dynamicSizeOptions, + maxSize: 15, + }; + }, + } as unknown as DynamicSizeProperty + } + /> + ); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); + +test('Should render legend with only max marker when size difference does not provide enough vertical space for more labels', async () => { + const component = shallow( + { + return { + ...dynamicSizeOptions, + maxSize: 11, + }; + }, + } as unknown as DynamicSizeProperty + } + /> + ); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); + +test('Should render legend without label cutoff when min size is 1', async () => { + const component = shallow( + { + return { + ...dynamicSizeOptions, + minSize: 1, + maxSize: 7, + }; + }, + } as unknown as DynamicSizeProperty + } + /> + ); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); + +test('Should render max label with std clamp notification', async () => { + const component = shallow( + { + return { + ...dynamicSizeOptions, + maxSize: 11, + }; + }, + getRangeFieldMeta: () => { + return { + min: 0, + max: 16000, + delta: 16000, + isMaxOutsideStdRange: true, + }; + }, + } as unknown as DynamicSizeProperty + } + /> + ); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); + +test('Should invert legend', async () => { + const component = shallow( + { + return { + ...dynamicSizeOptions, + maxSize: 24, + invert: true, + }; + }, + } as unknown as DynamicSizeProperty + } + /> + ); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_size_legend.tsx similarity index 67% rename from x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx rename to x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_size_legend.tsx index 11deb6e55942e2..c00557a2d13829 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/marker_size_legend.tsx @@ -9,13 +9,14 @@ import React, { Component } from 'react'; import _ from 'lodash'; import { euiThemeVars } from '@kbn/ui-theme'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; -import { RangeFieldMeta } from '../../../../../../common/descriptor_types'; -import { DynamicSizeProperty } from '../../properties/dynamic_size_property'; -import { RightAlignedText } from './right_aligned_text'; +import { RangeFieldMeta } from '../../../../../../../common/descriptor_types'; +import { DynamicSizeProperty } from '../../../properties/dynamic_size_property'; +import { RightAlignedText } from '../right_aligned_text'; +import { getMaxLabel, getMinLabel } from './get_ordinal_label'; +import { type Marker, MarkerList } from './marker_list'; const FONT_SIZE = 10; const HALF_FONT_SIZE = FONT_SIZE / 2; -const MIN_MARKER_DISTANCE = (FONT_SIZE + 2) / 2; const EMPTY_VALUE = ''; @@ -103,29 +104,34 @@ export class MarkerSizeLegend extends Component { const circleCenterX = options.maxSize + circleStyle.strokeWidth; const circleBottomY = svgHeight - circleStyle.strokeWidth; - const makeMarker = (radius: number, formattedValue: string | number) => { + const makeMarker = (radius: number, formattedValue: string | number): Marker => { const circleCenterY = circleBottomY - radius; const circleTopY = circleCenterY - radius; const textOffset = this.state.maxLabelWidth + HALF_FONT_SIZE; - return ( - - - - - - ); + const rawTextY = circleTopY + HALF_FONT_SIZE; + const textY = rawTextY > svgHeight ? svgHeight : rawTextY; + return { + svg: ( + + + + + + ), + textY, + }; }; function getMarkerRadius(percentage: number) { @@ -142,34 +148,33 @@ export class MarkerSizeLegend extends Component { return fieldMeta!.delta > 3 ? Math.round(value) : value; } - const markers = []; + const maxLabel = getMaxLabel( + this.props.style.isFieldMetaEnabled(), + Boolean(fieldMeta.isMaxOutsideStdRange), + this._formatValue(fieldMeta.max) + ); - if (fieldMeta.delta > 0) { - const smallestMarker = makeMarker( - options.minSize, - this._formatValue(invert ? fieldMeta.max : fieldMeta.min) - ); - markers.push(smallestMarker); - - const markerDelta = options.maxSize - options.minSize; - if (markerDelta > MIN_MARKER_DISTANCE * 3) { - markers.push(makeMarker(getMarkerRadius(0.25), this._formatValue(getValue(0.25)))); - markers.push(makeMarker(getMarkerRadius(0.5), this._formatValue(getValue(0.5)))); - markers.push(makeMarker(getMarkerRadius(0.75), this._formatValue(getValue(0.75)))); - } else if (markerDelta > MIN_MARKER_DISTANCE) { - markers.push(makeMarker(getMarkerRadius(0.5), this._formatValue(getValue(0.5)))); - } - } + const minLabel = getMinLabel( + this.props.style.isFieldMetaEnabled(), + Boolean(fieldMeta.isMinOutsideStdRange), + this._formatValue(fieldMeta.min) + ); - const largestMarker = makeMarker( - options.maxSize, - this._formatValue(invert ? fieldMeta.min : fieldMeta.max) + const markerList = new MarkerList( + FONT_SIZE, + makeMarker(options.maxSize, invert ? minLabel : maxLabel) ); - markers.push(largestMarker); + + if (fieldMeta.delta > 0) { + markerList.push(makeMarker(options.minSize, invert ? maxLabel : minLabel)); + markerList.push(makeMarker(getMarkerRadius(0.25), this._formatValue(getValue(0.25)))); + markerList.push(makeMarker(getMarkerRadius(0.5), this._formatValue(getValue(0.5)))); + markerList.push(makeMarker(getMarkerRadius(0.75), this._formatValue(getValue(0.75)))); + } return ( - {markers} + {markerList.getMarkers()} ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/ordinal_legend.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/ordinal_legend.test.tsx new file mode 100644 index 00000000000000..70da28a4d289fd --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/ordinal_legend.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../../../common/constants'; +import type { DynamicSizeProperty } from '../../../properties/dynamic_size_property'; +import type { IField } from '../../../../../fields/field'; +import { OrdinalLegend } from './ordinal_legend'; + +const dynamicSizeOptions = { + minSize: 1, + maxSize: 10, + field: { + name: 'bytes', + origin: FIELD_ORIGIN.SOURCE, + }, + fieldMetaOptions: { + isEnabled: true, + sigma: 3, + }, + invert: false, +}; + +const mockStyle = { + formatField: (value: number) => { + return `${value * 0.001}KB`; + }, + getDisplayStyleName: () => { + return 'Border width'; + }, + getField: () => { + return { + getLabel: () => { + return 'bytes'; + }, + } as unknown as IField; + }, + getOptions: () => { + return dynamicSizeOptions; + }, + getRangeFieldMeta: () => { + return { + min: 0, + max: 19000, + delta: 19000, + }; + }, + getStyleName: () => { + return VECTOR_STYLES.LINE_WIDTH; + }, + isFieldMetaEnabled: () => { + return true; + }, +} as unknown as DynamicSizeProperty; + +test('Should render legend', async () => { + const component = shallow(); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/ordinal_legend.tsx similarity index 85% rename from x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx rename to x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/ordinal_legend.tsx index 048d1bf12a218c..ba0edf541bbd6a 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/size/ordinal_legend.tsx @@ -8,10 +8,11 @@ import React, { Component, Fragment } from 'react'; import _ from 'lodash'; import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; -import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; -import { VECTOR_STYLES } from '../../../../../../common/constants'; -import { CircleIcon } from './circle_icon'; -import { IDynamicStyleProperty } from '../../properties/dynamic_style_property'; +import { RangedStyleLegendRow } from '../../../../components/ranged_style_legend_row'; +import { VECTOR_STYLES } from '../../../../../../../common/constants'; +import { CircleIcon } from '../circle_icon'; +import { IDynamicStyleProperty } from '../../../properties/dynamic_style_property'; +import { getMaxLabel, getMinLabel } from './get_ordinal_label'; function getLineWidthIcons() { const defaultStyle = { @@ -130,12 +131,18 @@ export class OrdinalLegend extends Component { let maxLabel: string | number = EMPTY_VALUE; if (fieldMeta) { const min = this._formatValue(_.get(fieldMeta, 'min', EMPTY_VALUE)); - minLabel = - this.props.style.isFieldMetaEnabled() && fieldMeta.isMinOutsideStdRange ? `< ${min}` : min; + minLabel = getMinLabel( + this.props.style.isFieldMetaEnabled(), + Boolean(fieldMeta.isMinOutsideStdRange), + min + ); const max = this._formatValue(_.get(fieldMeta, 'max', EMPTY_VALUE)); - maxLabel = - this.props.style.isFieldMetaEnabled() && fieldMeta.isMaxOutsideStdRange ? `> ${max}` : max; + maxLabel = getMaxLabel( + this.props.style.isFieldMetaEnabled(), + Boolean(fieldMeta.isMaxOutsideStdRange), + max + ); } const options = this.props.style.getOptions(); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap index b1fc94c4475ce4..ce716d0b542d98 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap @@ -1,236 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renderLegendDetailRow Should render icon size scale 1`] = ` +exports[`renderLegendDetailRow Should render marker size legend for icon size property 1`] = `
- - - - - - - foobar_label - - - - - - - - - - - - - - - - - - - - - - - + mockMarkerSizeLegend
`; -exports[`renderLegendDetailRow Should render line width simple range 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - } - invert={false} - maxLabel="100_format" - minLabel="0_format" - propertyLabel="Border width" -/> +exports[`renderLegendDetailRow Should render ordinal legend for line width style property 1`] = ` +
+ mockMarkerSizeLegend +
`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx index 9f92d81313da74..f7d0d611a4d954 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx @@ -11,6 +11,15 @@ jest.mock('../../components/vector_style_editor', () => ({ }, })); +jest.mock('../../components/legend/size', () => ({ + MarkerSizeLegend: () => { + return
mockMarkerSizeLegend
; + }, + OrdinalLegend: () => { + return
mockMarkerSizeLegend
; + }, +})); + import React from 'react'; import { shallow } from 'enzyme'; @@ -20,95 +29,37 @@ import { IField } from '../../../../fields/field'; import { IVectorLayer } from '../../../../layers/vector_layer'; describe('renderLegendDetailRow', () => { - test('Should render line width simple range', async () => { - const field = { - getLabel: async () => { - return 'foobar_label'; - }, - getName: () => { - return 'foodbar'; - }, - getOrigin: () => { - return FIELD_ORIGIN.SOURCE; - }, - supportsFieldMetaFromEs: () => { - return true; - }, - supportsFieldMetaFromLocalData: () => { - return true; - }, - } as unknown as IField; + test('Should render ordinal legend for line width style property', () => { const sizeProp = new DynamicSizeProperty( { minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } }, VECTOR_STYLES.LINE_WIDTH, - field, + {} as unknown as IField, {} as unknown as IVectorLayer, () => { return (value: RawValue) => value + '_format'; }, false ); - sizeProp.getRangeFieldMeta = () => { - return { - min: 0, - max: 100, - delta: 100, - }; - }; const legendRow = sizeProp.renderLegendDetailRow(); const component = shallow(legendRow); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); expect(component).toMatchSnapshot(); }); - test('Should render icon size scale', async () => { - const field = { - getLabel: async () => { - return 'foobar_label'; - }, - getName: () => { - return 'foodbar'; - }, - getOrigin: () => { - return FIELD_ORIGIN.SOURCE; - }, - supportsFieldMetaFromEs: () => { - return true; - }, - supportsFieldMetaFromLocalData: () => { - return true; - }, - } as unknown as IField; + test('Should render marker size legend for icon size property', () => { const sizeProp = new DynamicSizeProperty( { minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } }, VECTOR_STYLES.ICON_SIZE, - field, + {} as unknown as IField, {} as unknown as IVectorLayer, () => { return (value: RawValue) => value + '_format'; }, false ); - sizeProp.getRangeFieldMeta = () => { - return { - min: 0, - max: 100, - delta: 100, - }; - }; const legendRow = sizeProp.renderLegendDetailRow(); const component = shallow(legendRow); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx index dcaaa9ef227de6..c3a1bbbd2ac051 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx @@ -8,8 +8,7 @@ import React from 'react'; import type { Map as MbMap } from '@kbn/mapbox-gl'; import { DynamicStyleProperty } from '../dynamic_style_property'; -import { OrdinalLegend } from '../../components/legend/ordinal_legend'; -import { MarkerSizeLegend } from '../../components/legend/marker_size_legend'; +import { MarkerSizeLegend, OrdinalLegend } from '../../components/legend/size'; import { makeMbClampedNumberExpression } from '../../style_util'; import { FieldFormatter, diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/glyphs.test.ts b/x-pack/plugins/maps/public/connected_components/mb_map/glyphs.test.ts index 18cbd8c17f03b6..78df48a8c84987 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/glyphs.test.ts +++ b/x-pack/plugins/maps/public/connected_components/mb_map/glyphs.test.ts @@ -61,7 +61,7 @@ describe('EMS enabled', () => { await getCanAccessEmsFonts(); expect(getGlyphs()).toEqual({ isEmsFont: false, - glyphUrlTemplate: 'abc/api/maps/fonts/{fontstack}/{range}', + glyphUrlTemplate: 'abc/internal/maps/fonts/{fontstack}/{range}', }); }); }); @@ -108,7 +108,7 @@ describe('EMS disabled', () => { test('should return kibana fonts template URL before canAccessEmsFontsPromise resolves', () => { expect(getGlyphs()).toEqual({ isEmsFont: false, - glyphUrlTemplate: 'abc/api/maps/fonts/{fontstack}/{range}', + glyphUrlTemplate: 'abc/internal/maps/fonts/{fontstack}/{range}', }); }); @@ -116,7 +116,7 @@ describe('EMS disabled', () => { await getCanAccessEmsFonts(); expect(getGlyphs()).toEqual({ isEmsFont: false, - glyphUrlTemplate: 'abc/api/maps/fonts/{fontstack}/{range}', + glyphUrlTemplate: 'abc/internal/maps/fonts/{fontstack}/{range}', }); }); }); diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 71858ecb02459a..7653b2c59b357b 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -51,6 +51,7 @@ import type { MapExtentState } from '../../reducers/map/types'; import { CUSTOM_ICON_PIXEL_RATIO, createSdfIcon } from '../../classes/styles/vector/symbol_utils'; import { MAKI_ICONS } from '../../classes/styles/vector/maki_icons'; import { KeydownScrollZoom } from './keydown_scroll_zoom/keydown_scroll_zoom'; +import { transformRequest } from './transform_request'; export interface Props { isMapReady: boolean; @@ -171,6 +172,7 @@ export class MbMap extends Component { preserveDrawingBuffer: getPreserveDrawingBuffer(), maxZoom: this.props.settings.maxZoom, minZoom: this.props.settings.minZoom, + transformRequest, }; if (initialView) { options.zoom = initialView.zoom; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/transform_request.ts b/x-pack/plugins/maps/public/connected_components/mb_map/transform_request.ts new file mode 100644 index 00000000000000..47f14956d6dcae --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/mb_map/transform_request.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { + FONTS_API_PATH, + MVT_GETTILE_API_PATH, + MVT_GETGRIDTILE_API_PATH, +} from '../../../common/constants'; +import { getHttp } from '../../kibana_services'; + +const FONTS = getHttp().basePath.prepend(FONTS_API_PATH); +const GETTILE = getHttp().basePath.prepend(MVT_GETTILE_API_PATH); +const GETGRIDTILE = getHttp().basePath.prepend(MVT_GETGRIDTILE_API_PATH); + +export function transformRequest(url: string, resourceType: string | undefined) { + if (resourceType === 'Glyphs' && url.startsWith(FONTS)) { + return { + url, + method: 'GET' as 'GET', + headers: { [ELASTIC_HTTP_VERSION_HEADER]: '1' }, + }; + } + + if (resourceType === 'Tile' && url.startsWith(GETTILE)) { + return { + url, + method: 'GET' as 'GET', + headers: { [ELASTIC_HTTP_VERSION_HEADER]: '1' }, + }; + } + + if (resourceType === 'Tile' && url.startsWith(GETGRIDTILE)) { + return { + url, + method: 'GET' as 'GET', + headers: { [ELASTIC_HTTP_VERSION_HEADER]: '1' }, + }; + } + + return { url }; +} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx index 656d57e977d042..bf7cdf715b9a09 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/feature_draw_controls/feature_edit_tools/feature_edit_tools.tsx @@ -36,6 +36,7 @@ export function FeatureEditTools(props: Props) { const drawBBoxSelected = props.drawShape === DRAW_SHAPE.BOUNDS; const drawPointSelected = props.drawShape === DRAW_SHAPE.POINT; const deleteSelected = props.drawShape === DRAW_SHAPE.DELETE; + const isWaiting = props.drawShape === DRAW_SHAPE.WAIT; function toggleDrawShape(mode: DRAW_SHAPE) { if (mode && props.drawShape === mode) { @@ -70,6 +71,7 @@ export function FeatureEditTools(props: Props) { aria-pressed={drawLineSelected} isSelected={drawLineSelected} display={drawLineSelected ? 'fill' : 'empty'} + isDisabled={isWaiting} /> )} @@ -145,6 +150,7 @@ export function FeatureEditTools(props: Props) { aria-pressed={drawPointSelected} isSelected={drawPointSelected} display={drawPointSelected ? 'fill' : 'empty'} + isDisabled={isWaiting} /> diff --git a/x-pack/plugins/maps/public/inspector/vector_tile_adapter/components/get_tile_request.test.ts b/x-pack/plugins/maps/public/inspector/vector_tile_adapter/components/get_tile_request.test.ts index 91f9e02cd7c1cb..922d45b2effc72 100644 --- a/x-pack/plugins/maps/public/inspector/vector_tile_adapter/components/get_tile_request.test.ts +++ b/x-pack/plugins/maps/public/inspector/vector_tile_adapter/components/get_tile_request.test.ts @@ -11,7 +11,7 @@ test('Should return elasticsearch vector tile request for aggs tiles', () => { expect( getTileRequest({ layerId: '1', - tileUrl: `/pof/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=geo.coordinates&hasLabels=false&buffer=7&index=kibana_sample_data_logs&gridPrecision=8&requestBody=(_source%3A(excludes%3A!())%2Caggs%3A()%2Cfields%3A!((field%3A'%40timestamp'%2Cformat%3Adate_time)%2C(field%3Atimestamp%2Cformat%3Adate_time)%2C(field%3Autc_time%2Cformat%3Adate_time))%2Cquery%3A(bool%3A(filter%3A!((match_phrase%3A(machine.os.keyword%3Aios))%2C(range%3A(timestamp%3A(format%3Astrict_date_optional_time%2Cgte%3A'2022-04-22T16%3A46%3A00.744Z'%2Clte%3A'2022-04-29T16%3A46%3A05.345Z'))))%2Cmust%3A!()%2Cmust_not%3A!()%2Cshould%3A!()))%2Cruntime_mappings%3A(hour_of_day%3A(script%3A(source%3A'emit(doc%5B!'timestamp!'%5D.value.getHour())%3B')%2Ctype%3Along))%2Cscript_fields%3A()%2Csize%3A0%2Cstored_fields%3A!('*'))&renderAs=heatmap&token=e8bff005-ccea-464a-ae56-2061b4f8ce68`, + tileUrl: `/pof/internal/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=geo.coordinates&hasLabels=false&buffer=7&index=kibana_sample_data_logs&gridPrecision=8&requestBody=(_source%3A(excludes%3A!())%2Caggs%3A()%2Cfields%3A!((field%3A'%40timestamp'%2Cformat%3Adate_time)%2C(field%3Atimestamp%2Cformat%3Adate_time)%2C(field%3Autc_time%2Cformat%3Adate_time))%2Cquery%3A(bool%3A(filter%3A!((match_phrase%3A(machine.os.keyword%3Aios))%2C(range%3A(timestamp%3A(format%3Astrict_date_optional_time%2Cgte%3A'2022-04-22T16%3A46%3A00.744Z'%2Clte%3A'2022-04-29T16%3A46%3A05.345Z'))))%2Cmust%3A!()%2Cmust_not%3A!()%2Cshould%3A!()))%2Cruntime_mappings%3A(hour_of_day%3A(script%3A(source%3A'emit(doc%5B!'timestamp!'%5D.value.getHour())%3B')%2Ctype%3Along))%2Cscript_fields%3A()%2Csize%3A0%2Cstored_fields%3A!('*'))&renderAs=heatmap&token=e8bff005-ccea-464a-ae56-2061b4f8ce68`, x: 3, y: 0, z: 2, @@ -81,7 +81,7 @@ test('Should return elasticsearch vector tile request for hits tiles', () => { expect( getTileRequest({ layerId: '1', - tileUrl: `http://localhost:5601/zuv/api/maps/mvt/getTile/4/2/6.pbf?geometryFieldName=geo.coordinates&index=kibana_sample_data_logs&hasLabels=true&buffer=7&requestBody=(_source%3A!f%2Cfields%3A!((field%3A%27%40timestamp%27%2Cformat%3Aepoch_millis)%2Cbytes)%2Cquery%3A(bool%3A(filter%3A!((range%3A(timestamp%3A(format%3Astrict_date_optional_time%2Cgte%3A%272022-06-15T20%3A00%3A00.000Z%27%2Clte%3A%272022-06-16T20%3A48%3A02.517Z%27))))%2Cmust%3A!()%2Cmust_not%3A!()%2Cshould%3A!()))%2Cruntime_mappings%3A(hour_of_day%3A(script%3A(source%3A%27emit(doc%5B!%27timestamp!%27%5D.value.getHour())%3B%27)%2Ctype%3Along))%2Csize%3A10000%2Csort%3A!((%27%40timestamp%27%3A(order%3Adesc%2Cunmapped_type%3Aboolean))))&token=7afe7c2d-c96b-4bdb-9b5e-819aceac80a1`, + tileUrl: `http://localhost:5601/zuv/internal/maps/mvt/getTile/4/2/6.pbf?geometryFieldName=geo.coordinates&index=kibana_sample_data_logs&hasLabels=true&buffer=7&requestBody=(_source%3A!f%2Cfields%3A!((field%3A%27%40timestamp%27%2Cformat%3Aepoch_millis)%2Cbytes)%2Cquery%3A(bool%3A(filter%3A!((range%3A(timestamp%3A(format%3Astrict_date_optional_time%2Cgte%3A%272022-06-15T20%3A00%3A00.000Z%27%2Clte%3A%272022-06-16T20%3A48%3A02.517Z%27))))%2Cmust%3A!()%2Cmust_not%3A!()%2Cshould%3A!()))%2Cruntime_mappings%3A(hour_of_day%3A(script%3A(source%3A%27emit(doc%5B!%27timestamp!%27%5D.value.getHour())%3B%27)%2Ctype%3Along))%2Csize%3A10000%2Csort%3A!((%27%40timestamp%27%3A(order%3Adesc%2Cunmapped_type%3Aboolean))))&token=7afe7c2d-c96b-4bdb-9b5e-819aceac80a1`, x: 0, y: 0, z: 2, diff --git a/x-pack/plugins/maps/server/data_indexing/index_data.ts b/x-pack/plugins/maps/server/data_indexing/index_data.ts index 59c90bb279565d..e8525e1f3c8114 100644 --- a/x-pack/plugins/maps/server/data_indexing/index_data.ts +++ b/x-pack/plugins/maps/server/data_indexing/index_data.ts @@ -26,7 +26,7 @@ export async function writeDataToIndex( }) ); } - const settings: WriteSettings = { index, body: data, refresh: true }; + const settings: WriteSettings = { index, body: data, refresh: 'wait_for' }; const resp = await asCurrentUser.index(settings); // @ts-expect-error always false if (resp.result === 'Error') { diff --git a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts index 847aadb4470348..ef4eae327f3701 100644 --- a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts +++ b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts @@ -33,193 +33,229 @@ export function initIndexingRoutes({ dataPlugin: DataPluginStart; securityPlugin?: SecurityPluginStart; }) { - router.post( - { + router.versioned + .post({ path: `/${INDEX_SOURCE_API_PATH}`, - validate: { - body: schema.object({ - index: schema.string(), - mappings: schema.any(), - }), - }, + access: 'internal', options: { body: { accepts: ['application/json'], }, }, - }, - async (context, request, response) => { - const coreContext = await context.core; - const { index, mappings } = request.body; - const indexPatternsService = await dataPlugin.indexPatterns.dataViewsServiceFactory( - coreContext.savedObjects.client, - coreContext.elasticsearch.client.asCurrentUser, - request - ); - const result = await createDocSource( - index, - mappings, - coreContext.elasticsearch.client, - indexPatternsService - ); - if (result.success) { - return response.ok({ body: result }); - } else { - if (result.error) { - logger.error(result.error); + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: schema.object({ + index: schema.string(), + mappings: schema.any(), + }), + }, + }, + }, + async (context, request, response) => { + const coreContext = await context.core; + const { index, mappings } = request.body; + const indexPatternsService = await dataPlugin.indexPatterns.dataViewsServiceFactory( + coreContext.savedObjects.client, + coreContext.elasticsearch.client.asCurrentUser, + request + ); + const result = await createDocSource( + index, + mappings, + coreContext.elasticsearch.client, + indexPatternsService + ); + if (result.success) { + return response.ok({ body: result }); + } else { + if (result.error) { + logger.error(result.error); + } + return response.custom({ + body: result?.error?.message, + statusCode: 500, + }); } - return response.custom({ - body: result?.error?.message, - statusCode: 500, - }); } - } - ); + ); - router.post( - { + router.versioned + .post({ path: INDEX_FEATURE_PATH, - validate: { - body: schema.object({ - index: schema.string(), - data: schema.any(), - }), - }, + access: 'internal', options: { body: { accepts: ['application/json'], maxBytes: MAX_DRAWING_SIZE_BYTES, }, }, - }, - async (context, request, response) => { - const coreContext = await context.core; - const result = await writeDataToIndex( - request.body.index, - request.body.data, - coreContext.elasticsearch.client.asCurrentUser - ); - if (result.success) { - return response.ok({ body: result }); - } else { - logger.error(result.error); - return response.custom({ - body: result.error.message, - statusCode: 500, - }); - } - } - ); - - router.delete( - { - path: `${INDEX_FEATURE_PATH}/{featureId}`, - validate: { - params: schema.object({ - featureId: schema.string(), - }), - body: schema.object({ - index: schema.string(), - }), + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: schema.object({ + index: schema.string(), + data: schema.any(), + }), + }, + }, }, - }, - async (context, request, response) => { - try { + async (context, request, response) => { const coreContext = await context.core; - const resp = await coreContext.elasticsearch.client.asCurrentUser.delete({ - index: request.body.index, - id: request.params.featureId, - refresh: true, - }); - // @ts-expect-error always false - if (resp.result === 'Error') { - throw resp; - } else { - return response.ok({ body: { success: true } }); - } - } catch (error) { - logger.error(error); - const errorStatusCode = error.meta?.statusCode; - if (errorStatusCode === 401) { - return response.unauthorized({ - body: { - message: 'User not authorized to delete indexed feature', - }, - }); - } else if (errorStatusCode === 403) { - return response.forbidden({ - body: { - message: 'Access to delete indexed feature forbidden', - }, - }); - } else if (errorStatusCode === 404) { - return response.notFound({ - body: { message: 'Feature not found' }, - }); + const result = await writeDataToIndex( + request.body.index, + request.body.data, + coreContext.elasticsearch.client.asCurrentUser + ); + if (result.success) { + return response.ok({ body: result }); } else { + logger.error(result.error); return response.custom({ - body: 'Unknown error deleting feature', + body: result.error.message, statusCode: 500, }); } } - } - ); + ); + + router.versioned + .delete({ + path: `${INDEX_FEATURE_PATH}/{featureId}`, + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + featureId: schema.string(), + }), + body: schema.object({ + index: schema.string(), + }), + }, + }, + }, + async (context, request, response) => { + try { + const coreContext = await context.core; + const resp = await coreContext.elasticsearch.client.asCurrentUser.delete({ + index: request.body.index, + id: request.params.featureId, + refresh: 'wait_for', + }); + // @ts-expect-error always false + if (resp.result === 'Error') { + throw resp; + } else { + return response.ok({ body: { success: true } }); + } + } catch (error) { + logger.error(error); + const errorStatusCode = error.meta?.statusCode; + if (errorStatusCode === 401) { + return response.unauthorized({ + body: { + message: 'User not authorized to delete indexed feature', + }, + }); + } else if (errorStatusCode === 403) { + return response.forbidden({ + body: { + message: 'Access to delete indexed feature forbidden', + }, + }); + } else if (errorStatusCode === 404) { + return response.notFound({ + body: { message: 'Feature not found' }, + }); + } else { + return response.custom({ + body: 'Unknown error deleting feature', + statusCode: 500, + }); + } + } + } + ); - router.get( - { + router.versioned + .get({ path: GET_MATCHING_INDEXES_PATH, - validate: { - query: schema.object({ - indexPattern: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: schema.object({ + indexPattern: schema.string(), + }), + }, + }, }, - }, - async (context, request, response) => { - const coreContext = await context.core; - return await getMatchingIndexes( - request.query.indexPattern, - coreContext.elasticsearch.client, - response, - logger - ); - } - ); + async (context, request, response) => { + const coreContext = await context.core; + return await getMatchingIndexes( + request.query.indexPattern, + coreContext.elasticsearch.client, + response, + logger + ); + } + ); - router.get( - { + router.versioned + .get({ path: CHECK_IS_DRAWING_INDEX, - validate: { - query: schema.object({ - index: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: schema.object({ + index: schema.string(), + }), + }, + }, }, - }, - async (context, request, response) => { - const { index } = request.query; - try { - const coreContext = await context.core; - const mappingsResp = - await coreContext.elasticsearch.client.asCurrentUser.indices.getMapping({ - index: request.query.index, + async (context, request, response) => { + const { index } = request.query; + try { + const coreContext = await context.core; + const mappingsResp = + await coreContext.elasticsearch.client.asCurrentUser.indices.getMapping({ + index: request.query.index, + }); + const isDrawingIndex = + mappingsResp[index].mappings?._meta?.created_by === + MAPS_NEW_VECTOR_LAYER_META_CREATED_BY; + return response.ok({ + body: { + success: true, + isDrawingIndex, + }, }); - const isDrawingIndex = - mappingsResp[index].mappings?._meta?.created_by === MAPS_NEW_VECTOR_LAYER_META_CREATED_BY; - return response.ok({ - body: { - success: true, - isDrawingIndex, - }, - }); - } catch (error) { - // Index likely doesn't exist - return response.ok({ - body: { - success: false, - error, - }, - }); + } catch (error) { + // Index likely doesn't exist + return response.ok({ + body: { + success: false, + error, + }, + }); + } } - } - ); + ); } diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index ed9235f8a5ae6b..2900bd072318a3 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -17,7 +17,6 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { APP_ID, MVT_GETTILE_API_PATH, - API_ROOT_PATH, MVT_GETGRIDTILE_API_PATH, RENDER_AS, } from '../../common/constants'; @@ -34,147 +33,161 @@ export function initMVTRoutes({ logger: Logger; core: CoreStart; }) { - router.get( - { - path: `${API_ROOT_PATH}/${MVT_GETTILE_API_PATH}/{z}/{x}/{y}.pbf`, - validate: { - params: schema.object({ - x: schema.number(), - y: schema.number(), - z: schema.number(), - }), - query: schema.object({ - buffer: schema.maybe(schema.number()), - geometryFieldName: schema.string(), - hasLabels: schema.boolean(), - requestBody: schema.string(), - index: schema.string(), - token: schema.maybe(schema.string()), - executionContextId: schema.maybe(schema.string()), - }), + router.versioned + .get({ + path: `${MVT_GETTILE_API_PATH}/{z}/{x}/{y}.pbf`, + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + x: schema.number(), + y: schema.number(), + z: schema.number(), + }), + query: schema.object({ + buffer: schema.maybe(schema.number()), + geometryFieldName: schema.string(), + hasLabels: schema.boolean(), + requestBody: schema.string(), + index: schema.string(), + token: schema.maybe(schema.string()), + executionContextId: schema.maybe(schema.string()), + }), + }, + }, }, - }, - async ( - context: DataRequestHandlerContext, - request: KibanaRequest, unknown>, - response: KibanaResponseFactory - ) => { - const { query, params } = request; - const x = parseInt((params as any).x, 10) as number; - const y = parseInt((params as any).y, 10) as number; - const z = parseInt((params as any).z, 10) as number; + async ( + context: DataRequestHandlerContext, + request: KibanaRequest, unknown>, + response: KibanaResponseFactory + ) => { + const { query, params } = request; + const x = parseInt((params as any).x, 10) as number; + const y = parseInt((params as any).y, 10) as number; + const z = parseInt((params as any).z, 10) as number; - let tileRequest: { path: string; body: estypes.SearchMvtRequest['body'] } = { - path: '', - body: {}, - }; - try { - tileRequest = getHitsTileRequest({ - buffer: 'buffer' in query ? parseInt(query.buffer, 10) : 5, - risonRequestBody: query.requestBody as string, - geometryFieldName: query.geometryFieldName as string, - hasLabels: query.hasLabels as boolean, - index: query.index as string, - x, - y, - z, - }); - } catch (e) { - return response.badRequest(); - } + let tileRequest: { path: string; body: estypes.SearchMvtRequest['body'] } = { + path: '', + body: {}, + }; + try { + tileRequest = getHitsTileRequest({ + buffer: 'buffer' in query ? parseInt(query.buffer, 10) : 5, + risonRequestBody: query.requestBody as string, + geometryFieldName: query.geometryFieldName as string, + hasLabels: query.hasLabels as boolean, + index: query.index as string, + x, + y, + z, + }); + } catch (e) { + return response.badRequest(); + } - const { stream, headers, statusCode } = await getTile({ - abortController: makeAbortController(request), - body: tileRequest.body, - context, - core, - executionContext: makeExecutionContext({ - type: 'server', - name: APP_ID, - description: 'mvt:get_hits_tile', - url: `${API_ROOT_PATH}/${MVT_GETTILE_API_PATH}/${z}/${x}/${y}.pbf`, - id: query.executionContextId, - }), - logger, - path: tileRequest.path, - }); + const { stream, headers, statusCode } = await getTile({ + abortController: makeAbortController(request), + body: tileRequest.body, + context, + core, + executionContext: makeExecutionContext({ + type: 'server', + name: APP_ID, + description: 'mvt:get_hits_tile', + url: `${MVT_GETTILE_API_PATH}/${z}/${x}/${y}.pbf`, + id: query.executionContextId, + }), + logger, + path: tileRequest.path, + }); - return sendResponse(response, stream, headers, statusCode); - } - ); + return sendResponse(response, stream, headers, statusCode); + } + ); - router.get( - { - path: `${API_ROOT_PATH}/${MVT_GETGRIDTILE_API_PATH}/{z}/{x}/{y}.pbf`, - validate: { - params: schema.object({ - x: schema.number(), - y: schema.number(), - z: schema.number(), - }), - query: schema.object({ - buffer: schema.maybe(schema.number()), - geometryFieldName: schema.string(), - hasLabels: schema.boolean(), - requestBody: schema.string(), - index: schema.string(), - renderAs: schema.string(), - token: schema.maybe(schema.string()), - gridPrecision: schema.number(), - executionContextId: schema.maybe(schema.string()), - }), + router.versioned + .get({ + path: `${MVT_GETGRIDTILE_API_PATH}/{z}/{x}/{y}.pbf`, + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + x: schema.number(), + y: schema.number(), + z: schema.number(), + }), + query: schema.object({ + buffer: schema.maybe(schema.number()), + geometryFieldName: schema.string(), + hasLabels: schema.boolean(), + requestBody: schema.string(), + index: schema.string(), + renderAs: schema.string(), + token: schema.maybe(schema.string()), + gridPrecision: schema.number(), + executionContextId: schema.maybe(schema.string()), + }), + }, + }, }, - }, - async ( - context: DataRequestHandlerContext, - request: KibanaRequest, unknown>, - response: KibanaResponseFactory - ) => { - const { query, params } = request; - const x = parseInt((params as any).x, 10) as number; - const y = parseInt((params as any).y, 10) as number; - const z = parseInt((params as any).z, 10) as number; + async ( + context: DataRequestHandlerContext, + request: KibanaRequest, unknown>, + response: KibanaResponseFactory + ) => { + const { query, params } = request; + const x = parseInt((params as any).x, 10) as number; + const y = parseInt((params as any).y, 10) as number; + const z = parseInt((params as any).z, 10) as number; - let tileRequest: { path: string; body: estypes.SearchMvtRequest['body'] } = { - path: '', - body: {}, - }; - try { - tileRequest = getAggsTileRequest({ - buffer: 'buffer' in query ? parseInt(query.buffer, 10) : 5, - risonRequestBody: query.requestBody as string, - geometryFieldName: query.geometryFieldName as string, - gridPrecision: parseInt(query.gridPrecision, 10), - hasLabels: query.hasLabels as boolean, - index: query.index as string, - renderAs: query.renderAs as RENDER_AS, - x, - y, - z, - }); - } catch (e) { - return response.badRequest(); - } + let tileRequest: { path: string; body: estypes.SearchMvtRequest['body'] } = { + path: '', + body: {}, + }; + try { + tileRequest = getAggsTileRequest({ + buffer: 'buffer' in query ? parseInt(query.buffer, 10) : 5, + risonRequestBody: query.requestBody as string, + geometryFieldName: query.geometryFieldName as string, + gridPrecision: parseInt(query.gridPrecision, 10), + hasLabels: query.hasLabels as boolean, + index: query.index as string, + renderAs: query.renderAs as RENDER_AS, + x, + y, + z, + }); + } catch (e) { + return response.badRequest(); + } - const { stream, headers, statusCode } = await getTile({ - abortController: makeAbortController(request), - body: tileRequest.body, - context, - core, - executionContext: makeExecutionContext({ - type: 'server', - name: APP_ID, - description: 'mvt:get_aggs_tile', - url: `${API_ROOT_PATH}/${MVT_GETGRIDTILE_API_PATH}/${z}/${x}/${y}.pbf`, - id: query.executionContextId, - }), - logger, - path: tileRequest.path, - }); + const { stream, headers, statusCode } = await getTile({ + abortController: makeAbortController(request), + body: tileRequest.body, + context, + core, + executionContext: makeExecutionContext({ + type: 'server', + name: APP_ID, + description: 'mvt:get_aggs_tile', + url: `${MVT_GETGRIDTILE_API_PATH}/${z}/${x}/${y}.pbf`, + id: query.executionContextId, + }), + logger, + path: tileRequest.path, + }); - return sendResponse(response, stream, headers, statusCode); - } - ); + return sendResponse(response, stream, headers, statusCode); + } + ); } async function getTile({ diff --git a/x-pack/plugins/maps/server/routes.ts b/x-pack/plugins/maps/server/routes.ts index 427f403927d5d9..347cae9e04b47b 100644 --- a/x-pack/plugins/maps/server/routes.ts +++ b/x-pack/plugins/maps/server/routes.ts @@ -21,79 +21,93 @@ export async function initRoutes(coreSetup: CoreSetup, logger: Logger): Promise< const [coreStart, { data: dataPlugin }]: [CoreStart, StartDeps] = (await coreSetup.getStartServices()) as unknown as [CoreStart, StartDeps]; - router.get( - { + router.versioned + .get({ path: `/${FONTS_API_PATH}/{fontstack}/{range}`, - validate: { - params: schema.object({ - fontstack: schema.string(), - range: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + fontstack: schema.string(), + range: schema.string(), + }), + }, + }, }, - }, - (context, request, response) => { - const range = path.normalize(request.params.range); - const rootPath = path.resolve(__dirname, 'fonts', 'open_sans'); - const fontPath = path.resolve(rootPath, `${range}.pbf`); - return !fontPath.startsWith(rootPath) - ? response.notFound() - : new Promise((resolve) => { - fs.readFile(fontPath, (error, data) => { - if (error) { - resolve(response.notFound()); - } else { - resolve( - response.ok({ - body: data, - }) - ); - } + (context, request, response) => { + const range = path.normalize(request.params.range); + const rootPath = path.resolve(__dirname, 'fonts', 'open_sans'); + const fontPath = path.resolve(rootPath, `${range}.pbf`); + return !fontPath.startsWith(rootPath) + ? response.notFound() + : new Promise((resolve) => { + fs.readFile(fontPath, (error, data) => { + if (error) { + resolve(response.notFound()); + } else { + resolve( + response.ok({ + body: data, + }) + ); + } + }); }); - }); - } - ); + } + ); - router.get( - { + router.versioned + .get({ path: `/${INDEX_SETTINGS_API_PATH}`, - validate: { - query: schema.object({ - indexPatternTitle: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: schema.object({ + indexPatternTitle: schema.string(), + }), + }, + }, }, - }, - async (context, request, response) => { - const { query } = request; - if (!query.indexPatternTitle) { - logger.warn(`Required query parameter 'indexPatternTitle' not provided.`); - return response.custom({ - body: `Required query parameter 'indexPatternTitle' not provided.`, - statusCode: 400, - }); - } + async (context, request, response) => { + const { query } = request; + if (!query.indexPatternTitle) { + logger.warn(`Required query parameter 'indexPatternTitle' not provided.`); + return response.custom({ + body: `Required query parameter 'indexPatternTitle' not provided.`, + statusCode: 400, + }); + } - try { - const coreContext = await context.core; - const resp = await coreContext.elasticsearch.client.asCurrentUser.indices.getSettings({ - index: query.indexPatternTitle, - }); - const indexPatternSettings = getIndexPatternSettings( - resp as unknown as Record - ); - return response.ok({ - body: indexPatternSettings, - }); - } catch (error) { - logger.warn( - `Cannot load index settings for data view '${query.indexPatternTitle}', error: ${error.message}.` - ); - return response.custom({ - body: 'Error loading index settings', - statusCode: 400, - }); + try { + const coreContext = await context.core; + const resp = await coreContext.elasticsearch.client.asCurrentUser.indices.getSettings({ + index: query.indexPatternTitle, + }); + const indexPatternSettings = getIndexPatternSettings( + resp as unknown as Record + ); + return response.ok({ + body: indexPatternSettings, + }); + } catch (error) { + logger.warn( + `Cannot load index settings for data view '${query.indexPatternTitle}', error: ${error.message}.` + ); + return response.custom({ + body: 'Error loading index settings', + statusCode: 400, + }); + } } - } - ); + ); initMVTRoutes({ router, logger, core: coreStart }); initIndexingRoutes({ router, logger, dataPlugin }); diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 0b129c7985a0ee..6f24d4d7d3c5ed 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -70,6 +70,7 @@ "@kbn/content-management-utils", "@kbn/core-saved-objects-server", "@kbn/maps-vector-tile-utils", + "@kbn/core-http-common", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ml/common/util/alerts.ts b/x-pack/plugins/ml/common/util/alerts.ts index 6c997dfb715664..db74701992e1eb 100644 --- a/x-pack/plugins/ml/common/util/alerts.ts +++ b/x-pack/plugins/ml/common/util/alerts.ts @@ -21,7 +21,7 @@ const narrowBucketLength = 60; */ export function resolveLookbackInterval(jobs: Job[], datafeeds: Datafeed[]): string { const bucketSpanInSeconds = Math.ceil( - resolveMaxTimeInterval(jobs.map((v) => v.analysis_config.bucket_span)) ?? 0 + resolveMaxTimeInterval(jobs.map((v) => v.analysis_config.bucket_span!)) ?? 0 ); const queryDelayInSeconds = Math.ceil( resolveMaxTimeInterval(datafeeds.map((v) => v.query_delay).filter(isDefined)) ?? 0 @@ -45,7 +45,7 @@ export function getLookbackInterval(jobs: CombinedJobWithStats[]): string { } export function getTopNBuckets(job: Job): number { - const bucketSpan = parseInterval(job.analysis_config.bucket_span); + const bucketSpan = parseInterval(job.analysis_config.bucket_span!); if (bucketSpan === null) { throw new Error('Unable to resolve a bucket span length'); diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 2b3f3d82042d1a..01419d51f3c008 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -133,7 +133,7 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex const { detectors } = job.analysis_config; if (detectorIndex >= 0 && detectorIndex < detectors.length) { const dtr = detectors[detectorIndex]; - const functionName = dtr.function; + const functionName = dtr.function as ML_JOB_AGGREGATION; // Check that the function maps to an ES aggregation, // and that the partitioning field isn't mlcategory @@ -334,7 +334,7 @@ export function isJobVersionGte(job: CombinedJob, version: string): boolean { // Note that the 'function' field in a record contains what the user entered e.g. 'high_count', // whereas the 'function_description' field holds an ML-built display hint for function e.g. 'count'. export function mlFunctionToESAggregation( - functionName: ML_JOB_AGGREGATION | string + functionName?: ML_JOB_AGGREGATION | string ): ES_AGGREGATION | null { if ( functionName === ML_JOB_AGGREGATION.MEAN || diff --git a/x-pack/plugins/ml/public/alerting/config_validator.tsx b/x-pack/plugins/ml/public/alerting/config_validator.tsx index 0a6df6978e67e1..3afc02dc60600f 100644 --- a/x-pack/plugins/ml/public/alerting/config_validator.tsx +++ b/x-pack/plugins/ml/public/alerting/config_validator.tsx @@ -50,7 +50,7 @@ export const ConfigValidator: FC = React.memo( lookbackIntervalInSeconds && alertIntervalInSeconds < lookbackIntervalInSeconds; - const bucketSpanDuration = parseInterval(jobConfigs[0].analysis_config.bucket_span); + const bucketSpanDuration = parseInterval(jobConfigs[0].analysis_config.bucket_span!); const notificationDuration = bucketSpanDuration ? Math.ceil(bucketSpanDuration.asMinutes()) * Math.min( diff --git a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx b/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx index f9fdfb17b319c2..b9a4054879ec1d 100644 --- a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx @@ -145,7 +145,7 @@ const MlAnomalyAlertTrigger: FC = ({ const maxNumberOfBuckets = useMemo(() => { if (jobConfigs.length === 0) return; - const bucketDuration = parseInterval(jobConfigs[0].analysis_config.bucket_span); + const bucketDuration = parseInterval(jobConfigs[0].analysis_config.bucket_span!); const lookbackIntervalDuration = advancedSettings.lookbackInterval ? parseInterval(advancedSettings.lookbackInterval) diff --git a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts index 31c4f81545dcb3..61e730ed487083 100644 --- a/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts +++ b/x-pack/plugins/ml/public/application/components/custom_urls/custom_url_editor/utils.ts @@ -389,7 +389,7 @@ function buildAppStateQueryParam(queryFieldNames: string[]) { // may contain dollar delimited partition / influencer entity tokens and // drilldown time range settings. async function getAnomalyDetectionJobTestUrl(job: Job, customUrl: MlUrlConfig): Promise { - const interval = parseInterval(job.analysis_config.bucket_span); + const interval = parseInterval(job.analysis_config.bucket_span!); const bucketSpanSecs = interval !== null ? interval.asSeconds() : 0; // By default, return configured url_value. Look to substitute any dollar-delimited @@ -462,6 +462,7 @@ async function getAnomalyDetectionJobTestUrl(job: Job, customUrl: MlUrlConfig): undefined, jobConfig, datafeedConfig + // @ts-expect-error TODO: fix after elasticsearch-js bump )) as unknown as estypes.MlPreviewDatafeedResponse>['data']; const docTimeFieldName = job.data_description.time_field; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx index fc6a913544169e..33b6099565bd35 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx @@ -102,7 +102,7 @@ export const RevertModelSnapshotFlyout: FC = ({ }, [calendarEvents]); const createChartData = useCallback(async () => { - const bucketSpanMs = parseInterval(job.analysis_config.bucket_span)!.asMilliseconds(); + const bucketSpanMs = parseInterval(job.analysis_config.bucket_span!)!.asMilliseconds(); const eventRate = await loadEventRateForJob(job, bucketSpanMs, 100); const anomalyData = await loadAnomalyDataForJob(job, bucketSpanMs, 100); setEventRateData(eventRate); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts b/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts index f6744dbd272b06..6b441f78da453a 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts @@ -141,7 +141,7 @@ export interface SourceIndicesWithGeoFields { // create new job objects based on standard job config objects export function createJobs(jobs: CombinedJob[]): ExplorerJob[] { return jobs.map((job) => { - const bucketSpan = parseInterval(job.analysis_config.bucket_span); + const bucketSpan = parseInterval(job.analysis_config.bucket_span!); return { id: job.job_id, selected: false, diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts index 8a8db92dcc45f2..589a4258503de9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -237,7 +237,7 @@ export class CategorizationJobCreator extends JobCreator { ? ML_JOB_AGGREGATION.COUNT : ML_JOB_AGGREGATION.RARE; - const bs = job.analysis_config.bucket_span; + const bs = job.analysis_config.bucket_span!; this.setDetectorType(detectorType); if (dtr.partitionField !== null) { this.categorizationPerPartitionField = dtr.partitionField.id; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index d1c03647910641..0a6acf72791fdf 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -191,7 +191,7 @@ export class JobCreator { } public get bucketSpan(): BucketSpan { - return this._job_config.analysis_config.bucket_span; + return this._job_config.analysis_config.bucket_span!; } protected _setBucketSpanMs(bucketSpan: BucketSpan) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts index b12d9aa004d980..634699abe53d20 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts @@ -59,7 +59,7 @@ export class SingleMetricJobCreator extends JobCreator { // overriding set means we need to override get too // JS doesn't do inheritance very well public get bucketSpan(): BucketSpan { - return this._job_config.analysis_config.bucket_span; + return this._job_config.analysis_config.bucket_span!; } // aggregations need to be recreated whenever the detector or bucket_span change diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index a933fdc0af5d5b..ea409cfa0870af 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -75,7 +75,7 @@ export function getRichDetectors( } return { - agg: newJobCapsService.getAggById(d.function), + agg: newJobCapsService.getAggById(d.function!), field, byField, overField, diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts index ad42adad33cd53..5954ed710c5f2d 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts @@ -335,7 +335,7 @@ export abstract class InferenceBase { } private getDefaultInferenceConfig(): estypes.MlInferenceConfigUpdateContainer[keyof estypes.MlInferenceConfigUpdateContainer] { - return this.model.inference_config[ + return this.model.inference_config![ this.inferenceType as keyof estypes.MlInferenceConfigUpdateContainer ]; } diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/common.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/common.ts index e1a0ae6438e11a..db40b056e8f7ae 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/common.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/common.ts @@ -55,7 +55,7 @@ export function processInferenceResult( inferenceResult: InferenceResult, model: estypes.MlTrainedModelConfig ): FormattedTextClassificationResponse { - const labels: string[] = model.inference_config.text_classification?.classification_labels ?? []; + const labels: string[] = model.inference_config?.text_classification?.classification_labels ?? []; let formattedResponse = [ { diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx index c719aa7368cfae..06ff8128aa1736 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx +++ b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx @@ -37,7 +37,7 @@ export const SelectedModel: FC = ({ model, inputType, deploymentId }) => const inferrer = useMemo(() => { if (model.model_type === TRAINED_MODEL_TYPE.PYTORCH) { - const taskType = Object.keys(model.inference_config)[0]; + const taskType = Object.keys(model.inference_config ?? {})[0]; switch (taskType) { case SUPPORTED_PYTORCH_TASKS.NER: diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts b/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts index f97015773c908e..a9f3d895fca36d 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts @@ -21,7 +21,7 @@ export function isTestable(modelItem: ModelItem, checkForState = false) { if ( modelItem.model_type === TRAINED_MODEL_TYPE.PYTORCH && PYTORCH_TYPES.includes( - Object.keys(modelItem.inference_config)[0] as SupportedPytorchTasksType + Object.keys(modelItem.inference_config ?? {})[0] as SupportedPytorchTasksType ) && (checkForState === false || modelItem.stats?.deployment_stats?.some((v) => v.state === DEPLOYMENT_STATE.STARTED)) diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts index 5df5aa9f971d42..b7e16306527fa0 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts @@ -21,7 +21,7 @@ export function getViewableDetectors(selectedJob: CombinedJob): ViewableDetector viewableDetectors.push({ index, detector_description: dtr.detector_description, - function: dtr.function, + function: dtr.function!, }); } }); diff --git a/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts b/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts index c2a329a0ba6502..ff648d4ea7eff9 100644 --- a/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts +++ b/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts @@ -24,7 +24,7 @@ export function getJobsObservable( switchMap((jobsIds) => anomalyDetectorService.getJobs$(jobsIds)), map((jobs) => { const explorerJobs: ExplorerJob[] = jobs.map((job) => { - const bucketSpan = parseInterval(job.analysis_config.bucket_span); + const bucketSpan = parseInterval(job.analysis_config.bucket_span!); return { id: job.job_id, selected: true, diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 84241f58d04dca..68b3ed34915035 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -481,7 +481,7 @@ export function alertingServiceProvider( } const maxBucket = resolveMaxTimeInterval( - jobsResponse.map((v) => v.analysis_config.bucket_span) + jobsResponse.map((v) => v.analysis_config.bucket_span!) ); if (maxBucket === undefined) { @@ -620,7 +620,7 @@ export function alertingServiceProvider( const datafeeds = await datafeedsService.getDatafeedByJobId(jobIds); const maxBucketInSeconds = resolveMaxTimeInterval( - jobsResponse.map((v) => v.analysis_config.bucket_span) + jobsResponse.map((v) => v.analysis_config.bucket_span!) ); if (maxBucketInSeconds === undefined) { diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts index 45318a685d16c8..afa2d204f9e439 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/types.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts @@ -29,6 +29,7 @@ export interface MlInferTrainedModelRequest extends estypes.MlInferTrainedModelR deployment_id?: string; } +// @ts-expect-error TODO: fix after elasticsearch-js bump export interface MlClient extends Omit { anomalySearch: ReturnType['anomalySearch']; diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index de2869899b65f2..be9d6961cd6baf 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -80,7 +80,6 @@ export function jobsProvider( job_id: jobId, force: true, wait_for_completion: false, - // @ts-expect-error delete_user_annotations is not in types yet delete_user_annotations: deleteUserAnnotations, }); } @@ -166,7 +165,6 @@ export function jobsProvider( const { task } = await mlClient.resetJob({ job_id: jobId, wait_for_completion: false, - // @ts-expect-error delete_user_annotations is not in types yet delete_user_annotations: deleteUserAnnotations, }); results[jobId] = { reset: true, task }; @@ -254,7 +252,7 @@ export function jobsProvider( awaitingNodeAssignment: isJobAwaitingNodeAssignment(job), alertingRules: job.alerting_rules, jobTags: job.custom_settings?.job_tags ?? {}, - bucketSpanSeconds: parseInterval(job.analysis_config.bucket_span)!.asSeconds(), + bucketSpanSeconds: parseInterval(job.analysis_config.bucket_span!)!.asSeconds(), }; if (jobIds.find((j) => j === tempJob.id)) { diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index 815013da3cfd2a..6b085f3e5490e0 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -162,9 +162,9 @@ function combineAllRollupFields( rollupFields[fieldName] = conf.fields[fieldName]; } else { const aggs = conf.fields[fieldName]; - // @ts-expect-error fix type. our RollupFields type is better aggs.forEach((agg) => { if (rollupFields[fieldName].find((f) => f.agg === agg.agg) === null) { + // @ts-expect-error TODO: fix after elasticsearch-js bump rollupFields[fieldName].push(agg); } }); diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts b/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts index 7491506505820a..4477f423da25d9 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_datafeed_preview.ts @@ -46,7 +46,6 @@ export async function validateDatafeedPreview( job_config: tempJob, datafeed_config: datafeed, }, - // @ts-expect-error es client types are wrong start, end, }, diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts index c75303be46941b..8e280811fb8c71 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts @@ -81,7 +81,7 @@ export async function validateTimeRange( } // check for minimum time range (25 buckets or 2 hours, whichever is longer) - const interval = parseInterval(job.analysis_config.bucket_span, true); + const interval = parseInterval(job.analysis_config.bucket_span!, true); if (interval === null) { messages.push({ id: 'bucket_span_invalid' }); } else { diff --git a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts index ea282d78ada60c..ba70e62f7d59dd 100644 --- a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts +++ b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts @@ -734,7 +734,7 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu // Add extra properties used by the explorer dashboard charts. fullSeriesConfig.functionDescription = record.function_description; - const parsedBucketSpan = parseInterval(job.analysis_config.bucket_span); + const parsedBucketSpan = parseInterval(job.analysis_config.bucket_span!); if (parsedBucketSpan !== null) { fullSeriesConfig.bucketSpanSeconds = parsedBucketSpan.asSeconds(); } diff --git a/x-pack/plugins/ml/server/routes/management.ts b/x-pack/plugins/ml/server/routes/management.ts index 0179085869d36b..9c68c8fcef2612 100644 --- a/x-pack/plugins/ml/server/routes/management.ts +++ b/x-pack/plugins/ml/server/routes/management.ts @@ -139,7 +139,7 @@ export function managementRoutes({ router, routeGuard }: RouteInitialization) { state: modelStatsMapped[id].deployment_stats?.state ?? '', type: [ m.model_type, - ...Object.keys(m.inference_config), + ...Object.keys(m.inference_config!), ...(m.tags.includes(BUILT_IN_MODEL_TAG) ? [BUILT_IN_MODEL_TYPE] : []), ], spaces: modelSpaces.trainedModels[id] ?? [], diff --git a/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts b/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts index d12ad6c782b439..b55f9b62c02edc 100644 --- a/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts +++ b/x-pack/plugins/osquery/common/schemas/routes/saved_query/create_saved_query_request_schema.ts @@ -19,7 +19,6 @@ import { removedOrUndefined, ecsMappingOrUndefined, } from '@kbn/osquery-io-ts-types'; -import type { RequiredKeepUndefined } from '../../../types'; export const createSavedQueryRequestSchema = t.type({ id, @@ -36,9 +35,8 @@ export const createSavedQueryRequestSchema = t.type({ export type CreateSavedQueryRequestSchema = t.OutputOf; // This type is used after a decode since some things are defaults after a decode. -export type CreateSavedQueryRequestSchemaDecoded = Omit< - RequiredKeepUndefined>, - 'description' +export type CreateSavedQueryRequestSchemaDecoded = t.TypeOf< + typeof createSavedQueryRequestSchema > & { description: Description; }; diff --git a/x-pack/plugins/osquery/common/types.ts b/x-pack/plugins/osquery/common/types.ts index 9c1804674919a1..f66fe22baa787c 100644 --- a/x-pack/plugins/osquery/common/types.ts +++ b/x-pack/plugins/osquery/common/types.ts @@ -9,20 +9,3 @@ export const savedQuerySavedObjectType = 'osquery-saved-query'; export const packSavedObjectType = 'osquery-pack'; export const packAssetSavedObjectType = 'osquery-pack-asset'; export const usageMetricSavedObjectType = 'osquery-manager-usage-metric'; - -/** - * This makes any optional property the same as Required would but also has the - * added benefit of keeping your undefined. - * - * For example: - * type A = RequiredKeepUndefined<{ a?: undefined; b: number }>; - * - * will yield a type of: - * type A = { a: undefined; b: number; } - * - */ -export type RequiredKeepUndefined = { [K in keyof T]-?: [T[K]] } extends infer U - ? U extends Record - ? { [K in keyof U]: U[K][0] } - : never - : never; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts index ebfb02d3b7e23a..71f89c3bf4dbf0 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/add_integration.cy.ts @@ -37,7 +37,7 @@ describe('ALL - Add Integration', () => { before(() => { loadSavedQuery().then((data) => { - savedQueryId = data.id; + savedQueryId = data.saved_object_id; }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts index c685bc8434b443..ab4a93c3dfc1ba 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts @@ -64,8 +64,8 @@ describe('Alert Event Details', () => { before(() => { loadPack(packData).then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadRule().then((data) => { ruleId = data.id; @@ -107,12 +107,12 @@ describe('Alert Event Details', () => { before(() => { loadPack(packData).then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadPack(multiQueryPackData).then((data) => { - multiQueryPackId = data.id; - multiQueryPackName = data.attributes.name; + multiQueryPackId = data.saved_object_id; + multiQueryPackName = data.name; }); loadRule().then((data) => { ruleId = data.id; @@ -382,8 +382,8 @@ describe('Alert Event Details', () => { before(() => { loadPack(packData).then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadRule(true).then((data) => { ruleId = data.id; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts index 62fe96ce58f9ba..33d5d42660e46c 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/custom_space.cy.ts @@ -49,8 +49,8 @@ describe('ALL - Custom space', () => { }, space as string ).then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); }); }); @@ -96,7 +96,7 @@ describe('ALL - Custom space', () => { cy.contains('Packs').click(); cy.contains('Create pack').click(); cy.react('CustomItemAction', { - props: { item: { attributes: { name: packName } } }, + props: { item: { name: packName } }, }).click(); selectAllAgents(); cy.contains('Submit').click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/edit_saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/edit_saved_queries.cy.ts index a3f0b7341d7279..a9ec7ed1dd37d3 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/edit_saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/edit_saved_queries.cy.ts @@ -15,8 +15,8 @@ describe('ALL - Edit saved query', () => { before(() => { loadSavedQuery().then((data) => { - savedQueryId = data.id; - savedQueryName = data.attributes.id; + savedQueryId = data.saved_object_id; + savedQueryName = data.id; }); }); @@ -31,7 +31,7 @@ describe('ALL - Edit saved query', () => { it('by changing ecs mappings and platforms', () => { cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryName } } }, + props: { index: 1, item: { id: savedQueryName } }, }).click(); cy.contains('Custom key/value pairs.').should('exist'); cy.contains('Hours of uptime').should('exist'); @@ -72,7 +72,7 @@ describe('ALL - Edit saved query', () => { cy.wait(5000); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryName } } }, + props: { index: 1, item: { id: savedQueryName } }, }).click(); cy.contains('Custom key/value pairs').should('not.exist'); cy.contains('Hours of uptime').should('not.exist'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts index b15ac2c44a6dc7..a350aae9d05cb3 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query.cy.ts @@ -63,16 +63,16 @@ describe('ALL - Live Query', () => { }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); loadSavedQuery({ interval: '3600', query: 'select * from uptime;', ecs_mapping: {}, }).then((savedQuery) => { - savedQueryId = savedQuery.id; - savedQueryName = savedQuery.attributes.name; + savedQueryId = savedQuery.saved_object_id; + savedQueryName = savedQuery.name; }); loadCase('securitySolution').then((caseInfo) => { caseId = caseInfo.id; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts index 841466a1c3e4d4..5bc561baed7fec 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/metrics.cy.ts @@ -17,8 +17,8 @@ describe('ALL - Inventory', () => { before(() => { loadSavedQuery().then((data) => { - savedQueryId = data.id; - savedQueryName = data.attributes.id; + savedQueryId = data.saved_object_id; + savedQueryName = data.id; }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts index 829020c4bf4612..933e80435960b4 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/packs.cy.ts @@ -63,16 +63,16 @@ describe('ALL - Packs', () => { describe('Create and edit a pack', () => { before(() => { loadSavedQuery().then((data) => { - savedQueryId = data.id; - savedQueryName = data.attributes.id; + savedQueryId = data.saved_object_id; + savedQueryName = data.id; }); loadSavedQuery({ ecs_mapping: {}, interval: '3600', query: 'select * from uptime;', }).then((data) => { - nomappingSavedQueryId = data.id; - nomappingSavedQueryName = data.attributes.id; + nomappingSavedQueryId = data.saved_object_id; + nomappingSavedQueryName = data.id; }); loadSavedQuery({ ecs_mapping: { @@ -83,8 +83,8 @@ describe('ALL - Packs', () => { interval: '3600', query: 'select * from uptime;', }).then((data) => { - oneMappingSavedQueryId = data.id; - oneMappingSavedQueryName = data.attributes.id; + oneMappingSavedQueryId = data.saved_object_id; + oneMappingSavedQueryName = data.id; }); loadSavedQuery({ ecs_mapping: { @@ -101,8 +101,8 @@ describe('ALL - Packs', () => { interval: '3600', query: 'select * from uptime;', }).then((data) => { - multipleMappingsSavedQueryId = data.id; - multipleMappingsSavedQueryName = data.attributes.id; + multipleMappingsSavedQueryId = data.saved_object_id; + multipleMappingsSavedQueryName = data.id; }); }); @@ -290,8 +290,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -332,8 +332,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -365,8 +365,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -408,8 +408,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -452,8 +452,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -479,8 +479,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 60, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -537,8 +537,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -574,8 +574,8 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packId = pack.id; - packName = pack.attributes.name; + packId = pack.saved_object_id; + packName = pack.name; }); }); @@ -631,7 +631,7 @@ describe('ALL - Packs', () => { [savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' }, }, }).then((pack) => { - packName = pack.attributes.name; + packName = pack.name; }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index 094184a2ddf7b6..de537fe73b911f 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -76,8 +76,8 @@ describe('ALL - Saved queries', () => { }, }, }).then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); }); @@ -93,7 +93,7 @@ describe('ALL - Saved queries', () => { it('checks result type on prebuilt saved query', () => { cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: 'users_elastic' } } }, + props: { index: 1, item: { id: 'users_elastic' } }, }).click(); cy.getBySel('resultsTypeField').within(() => { cy.contains('Snapshot'); @@ -102,7 +102,7 @@ describe('ALL - Saved queries', () => { it('user can run prebuilt saved query and add to case', () => { cy.react('PlayButtonComponent', { - props: { savedQuery: { attributes: { id: 'users_elastic' } } }, + props: { savedQuery: { id: 'users_elastic' } }, }).click(); selectAllAgents(); @@ -114,7 +114,7 @@ describe('ALL - Saved queries', () => { it('user cant delete prebuilt saved query', () => { cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: 'users_elastic' } } }, + props: { index: 1, item: { id: 'users_elastic' } }, }).click(); cy.contains('Delete query').should('not.exist'); navigateTo('/app/osquery/saved_queries'); @@ -124,7 +124,7 @@ describe('ALL - Saved queries', () => { findFormFieldByRowsLabelAndType('ID', 'query-to-delete'); cy.contains('Save query').click(); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: 'query-to-delete' } } }, + props: { index: 1, item: { id: 'query-to-delete' } }, }).click(); deleteAndConfirm('query'); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts index 24152e485f63cf..9aed481152bd8f 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts @@ -35,8 +35,8 @@ describe('Alert Test', () => { }, }, }).then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadRule().then((data) => { ruleId = data.id; diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/reader.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/reader.cy.ts index bff5a01c13ce33..fb53aa3217202b 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/reader.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/reader.cy.ts @@ -24,12 +24,12 @@ describe('Reader - only READ', () => { before(() => { loadPack().then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadSavedQuery().then((data) => { - savedQueryId = data.id; - savedQueryName = data.attributes.id; + savedQueryId = data.saved_object_id; + savedQueryName = data.id; }); loadLiveQuery().then((data) => { liveQueryQuery = data.queries?.[0].query; @@ -51,11 +51,11 @@ describe('Reader - only READ', () => { cy.contains(savedQueryName); cy.contains('Add saved query').should('be.disabled'); cy.react('PlayButtonComponent', { - props: { savedQuery: { attributes: { id: savedQueryName } } }, + props: { savedQuery: { id: savedQueryName } }, options: { timeout: 3000 }, }).should('not.exist'); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryName } } }, + props: { index: 1, item: { id: savedQueryName } }, }).click(); cy.react('EuiFormRow', { props: { label: 'ID' } }) .getBySel('input') @@ -90,7 +90,7 @@ describe('Reader - only READ', () => { cy.getBySel('tablePaginationPopoverButton').click(); cy.getBySel('tablePagination-50-rows').click(); cy.react('ActiveStateSwitchComponent', { - props: { item: { attributes: { name: packName } } }, + props: { item: { name: packName } }, }) .find('button') .should('be.disabled'); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts index ceedb2126d50a4..3192c9a421a774 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts @@ -32,12 +32,12 @@ describe('T1 Analyst - READ + runSavedQueries ', () => { before(() => { loadPack().then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadSavedQuery().then((data) => { - savedQueryId = data.id; - savedQueryName = data.attributes.id; + savedQueryId = data.saved_object_id; + savedQueryName = data.id; }); loadLiveQuery().then((data) => { liveQueryQuery = data.queries?.[0].query; @@ -59,7 +59,7 @@ describe('T1 Analyst - READ + runSavedQueries ', () => { cy.contains(savedQueryName); cy.contains('Add saved query').should('be.disabled'); cy.react('PlayButtonComponent', { - props: { savedQuery: { attributes: { id: savedQueryName } } }, + props: { savedQuery: { id: savedQueryName } }, }) .should('not.be.disabled') .click(); @@ -105,7 +105,7 @@ describe('T1 Analyst - READ + runSavedQueries ', () => { cy.getBySel('tablePagination-50-rows').click(); cy.contains('Add pack').should('be.disabled'); cy.react('ActiveStateSwitchComponent', { - props: { item: { attributes: { name: packName } } }, + props: { item: { name: packName } }, }) .find('button') .should('be.disabled'); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/t2_analyst.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/t2_analyst.cy.ts index 2275a96a9abb2a..7e074f896d362b 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/t2_analyst.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/t2_analyst.cy.ts @@ -29,12 +29,12 @@ describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { before(() => { loadPack().then((data) => { - packId = data.id; - packName = data.attributes.name; + packId = data.saved_object_id; + packName = data.name; }); loadSavedQuery().then((data) => { - savedQueryId = data.id; - savedQueryName = data.attributes.id; + savedQueryId = data.saved_object_id; + savedQueryName = data.id; }); }); @@ -57,7 +57,7 @@ describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { cy.getBySel('tablePagination-50-rows').click(); cy.contains('Add pack').should('be.disabled'); cy.react('ActiveStateSwitchComponent', { - props: { item: { attributes: { name: packName } } }, + props: { item: { name: packName } }, }) .find('button') .should('be.disabled'); @@ -121,7 +121,7 @@ describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { it('to click the edit button and edit pack', () => { navigateTo('/app/osquery/saved_queries'); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryName } } }, + props: { index: 1, item: { id: savedQueryName } }, }).click(); cy.contains('Custom key/value pairs.').should('exist'); cy.contains('Hours of uptime').should('exist'); @@ -134,7 +134,7 @@ describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => { cy.wait(5000); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryName } } }, + props: { index: 1, item: { id: savedQueryName } }, }).click(); cy.contains('Custom key/value pairs').should('not.exist'); cy.contains('Hours of uptime').should('not.exist'); diff --git a/x-pack/plugins/osquery/cypress/tasks/integrations.ts b/x-pack/plugins/osquery/cypress/tasks/integrations.ts index 8ea6c0b7347585..09948eca70538f 100644 --- a/x-pack/plugins/osquery/cypress/tasks/integrations.ts +++ b/x-pack/plugins/osquery/cypress/tasks/integrations.ts @@ -75,7 +75,7 @@ export const interceptPackId = (cb: (packId: string) => void) => { cy.intercept('POST', '**/api/osquery/packs', (req) => { req.continue((res) => { if (res.body.data) { - cb(res.body.data.id); + cb(res.body.data.saved_object_id); } return res.send(res.body); diff --git a/x-pack/plugins/osquery/cypress/tasks/packs.ts b/x-pack/plugins/osquery/cypress/tasks/packs.ts index 87df25e7ac7e75..43a007394a485b 100644 --- a/x-pack/plugins/osquery/cypress/tasks/packs.ts +++ b/x-pack/plugins/osquery/cypress/tasks/packs.ts @@ -21,7 +21,7 @@ export const preparePack = (packName: string) => { export const deactivatePack = (packName: string) => { cy.react('ActiveStateSwitchComponent', { - props: { item: { attributes: { name: packName } } }, + props: { item: { name: packName } }, }).click(); closeModalIfVisible(); @@ -32,7 +32,7 @@ export const deactivatePack = (packName: string) => { export const activatePack = (packName: string) => { cy.react('ActiveStateSwitchComponent', { - props: { item: { attributes: { name: packName } } }, + props: { item: { name: packName } }, }).click(); closeModalIfVisible(); @@ -50,6 +50,6 @@ export const cleanupAllPrebuiltPacks = () => { some(pack.references, { type: 'osquery-pack-asset' }) ); - return Promise.all(prebuiltPacks?.map((pack) => cleanupPack(pack.id))); + return Promise.all(prebuiltPacks?.map((pack) => cleanupPack(pack.saved_object_id))); }); }; diff --git a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts index 13586d1650960d..a7c267e510cf73 100644 --- a/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts +++ b/x-pack/plugins/osquery/cypress/tasks/saved_queries.ts @@ -94,7 +94,7 @@ export const getSavedQueriesComplexTest = () => navigateTo('/app/osquery/saved_queries'); cy.contains(savedQueryId); cy.react('PlayButtonComponent', { - props: { savedQuery: { attributes: { id: savedQueryId } } }, + props: { savedQuery: { id: savedQueryId } }, }).click(); selectAllAgents(); submitQuery(); @@ -103,7 +103,7 @@ export const getSavedQueriesComplexTest = () => cy.contains('Saved queries').click(); cy.contains(savedQueryId); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryId } } }, + props: { index: 1, item: { id: savedQueryId } }, }).click(); findFormFieldByRowsLabelAndType('Description (optional)', ' Edited'); // Run in test configuration @@ -127,7 +127,7 @@ export const getSavedQueriesComplexTest = () => // delete saved query cy.contains(savedQueryId); cy.react('CustomItemAction', { - props: { index: 1, item: { attributes: { id: savedQueryId } } }, + props: { index: 1, item: { id: savedQueryId } }, }).click(); deleteAndConfirm('query'); cy.contains(savedQueryId).should('exist'); diff --git a/x-pack/plugins/osquery/public/assets/use_assets_status.ts b/x-pack/plugins/osquery/public/assets/use_assets_status.ts index 0d082f29b01751..ba0e60e937a2b9 100644 --- a/x-pack/plugins/osquery/public/assets/use_assets_status.ts +++ b/x-pack/plugins/osquery/public/assets/use_assets_status.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedObject } from '@kbn/core/public'; +import type { KibanaAssetReference } from '@kbn/fleet-plugin/common'; import { useQuery } from '@tanstack/react-query'; import { useKibana } from '../common/lib/kibana'; import { INTEGRATION_ASSETS_STATUS_ID } from './constants'; @@ -13,12 +13,12 @@ import { INTEGRATION_ASSETS_STATUS_ID } from './constants'; export const useAssetsStatus = () => { const { http } = useKibana().services; - return useQuery<{ install: SavedObject[]; update: SavedObject[]; upToDate: SavedObject[] }>( - [INTEGRATION_ASSETS_STATUS_ID], - () => http.get('/internal/osquery/assets'), - { - keepPreviousData: true, - retry: false, - } - ); + return useQuery<{ + install: KibanaAssetReference[]; + update: KibanaAssetReference[]; + upToDate: KibanaAssetReference[]; + }>([INTEGRATION_ASSETS_STATUS_ID], () => http.get('/internal/osquery/assets'), { + keepPreviousData: true, + retry: false, + }); }; diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index d56b981b128c84..badd67fe3bf84c 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -250,7 +250,7 @@ const LiveQueryFormComponent: React.FC = ({ setValue('queryType', 'pack'); if (!isPackDataFetched) return; - const selectedPackOption = find(packsData?.data, ['id', defaultValue.packId]); + const selectedPackOption = find(packsData?.data, ['saved_object_id', defaultValue.packId]); if (selectedPackOption) { setValue('packId', [defaultValue.packId]); } diff --git a/x-pack/plugins/osquery/public/live_queries/form/packs_combobox_field.tsx b/x-pack/plugins/osquery/public/live_queries/form/packs_combobox_field.tsx index 0d6c93d23ed306..a7615d5ce311d7 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/packs_combobox_field.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/packs_combobox_field.tsx @@ -81,11 +81,11 @@ export const PacksComboBoxField = ({ const packOptions = useMemo>>( () => fieldProps?.packsData?.map((packSO) => ({ - label: packSO.attributes.name ?? '', + label: packSO.name ?? '', value: { - id: packSO.id, - name: packSO.attributes.name, - description: packSO.attributes.description, + id: packSO.saved_object_id, + name: packSO.name, + description: packSO.description, }, })) ?? [], [fieldProps?.packsData] diff --git a/x-pack/plugins/osquery/public/packs/active_state_switch.tsx b/x-pack/plugins/osquery/public/packs/active_state_switch.tsx index cb67f248207fc7..8dfbe93d594db1 100644 --- a/x-pack/plugins/osquery/public/packs/active_state_switch.tsx +++ b/x-pack/plugins/osquery/public/packs/active_state_switch.tsx @@ -58,17 +58,17 @@ const ActiveStateSwitchComponent: React.FC = ({ item }) queryClient.invalidateQueries([PACKS_ID]); setErrorToast(); toasts.addSuccess( - response?.data?.attributes.enabled + response?.data?.enabled ? i18n.translate('xpack.osquery.pack.table.activatedSuccessToastMessageText', { defaultMessage: 'Successfully activated "{packName}" pack', values: { - packName: response?.data?.attributes.name, + packName: response?.data?.name, }, }) : i18n.translate('xpack.osquery.pack.table.deactivatedSuccessToastMessageText', { defaultMessage: 'Successfully deactivated "{packName}" pack', values: { - packName: response?.data?.attributes.name, + packName: response?.data?.name, }, }) ); @@ -77,7 +77,7 @@ const ActiveStateSwitchComponent: React.FC = ({ item }) }); const handleToggleActive = useCallback(() => { - mutateAsync({ id: item.id, enabled: !item.attributes.enabled }); + mutateAsync({ id: item.saved_object_id, enabled: !item.enabled }); hideConfirmationModal(); }, [hideConfirmationModal, item, mutateAsync]); @@ -93,7 +93,7 @@ const ActiveStateSwitchComponent: React.FC = ({ item }) <> {isLoading && } = ({ }; try { - if (editMode && defaultValue?.id) { - await updateAsync({ id: defaultValue?.id, ...serializer(values) }); + if (editMode && defaultValue?.saved_object_id) { + await updateAsync({ id: defaultValue?.saved_object_id, ...serializer(values) }); } else { await createAsync(serializer(values)); } // eslint-disable-next-line no-empty } catch (e) {} }, - [createAsync, defaultValue?.id, editMode, getShards, shards, updateAsync] + [createAsync, defaultValue?.saved_object_id, editMode, getShards, shards, updateAsync] ); const handleSubmitForm = useMemo(() => handleSubmit(onSubmit), [handleSubmit, onSubmit]); diff --git a/x-pack/plugins/osquery/public/packs/packs_table.tsx b/x-pack/plugins/osquery/public/packs/packs_table.tsx index cd80e9ecc86c74..d037010c64258d 100644 --- a/x-pack/plugins/osquery/public/packs/packs_table.tsx +++ b/x-pack/plugins/osquery/public/packs/packs_table.tsx @@ -42,8 +42,8 @@ const ScheduledQueryNameComponent = ({ id, name }: { id: string; name: string }) const ScheduledQueryName = React.memo(ScheduledQueryNameComponent); -const renderName = (_: unknown, item: { id: string; attributes: { name: string } }) => ( - +const renderName = (_: unknown, item: PackSavedObject) => ( + ); export const AgentPoliciesPopover = ({ agentPolicyIds = [] }: { agentPolicyIds?: string[] }) => { @@ -101,10 +101,7 @@ const PacksTableComponent = () => { const renderUpdatedAt = useCallback((updatedAt, item) => { if (!updatedAt) return '-'; - const updatedBy = - item.attributes.updated_by !== item.attributes.created_by - ? ` @ ${item.attributes.updated_by}` - : ''; + const updatedBy = item.updated_by !== item.created_by ? ` @ ${item.updated_by}` : ''; return updatedAt ? ( @@ -119,7 +116,7 @@ const PacksTableComponent = () => { (item) => () => push('/live_queries/new', { form: { - packId: item.id, + packId: item.saved_object_id, }, }), [push] @@ -130,7 +127,7 @@ const PacksTableComponent = () => { const playText = i18n.translate('xpack.osquery.packs.table.runActionAriaLabel', { defaultMessage: 'Run {packName}', values: { - packName: item.attributes.name, + packName: item.name, }, }); @@ -146,11 +143,11 @@ const PacksTableComponent = () => { const columns: Array> = useMemo( () => [ { - field: 'attributes.name', + field: 'name', name: i18n.translate('xpack.osquery.packs.table.nameColumnTitle', { defaultMessage: 'Name', }), - sortable: (item) => item.attributes.name.toLowerCase(), + sortable: (item) => item.name.toLowerCase(), render: renderName, }, { @@ -162,7 +159,7 @@ const PacksTableComponent = () => { render: renderAgentPolicy, }, { - field: 'attributes.queries', + field: 'queries', name: i18n.translate('xpack.osquery.packs.table.numberOfQueriesColumnTitle', { defaultMessage: 'Number of queries', }), @@ -170,7 +167,7 @@ const PacksTableComponent = () => { width: '150px', }, { - field: 'attributes.created_by', + field: 'created_by', name: i18n.translate('xpack.osquery.packs.table.createdByColumnTitle', { defaultMessage: 'Created by', }), @@ -178,14 +175,14 @@ const PacksTableComponent = () => { truncateText: true, }, { - field: 'attributes.updated_at', + field: 'updated_at', name: 'Last updated', sortable: (item) => (item.updated_at ? Date.parse(item.updated_at) : 0), truncateText: true, render: renderUpdatedAt, }, { - field: 'attributes.enabled', + field: 'enabled', name: i18n.translate('xpack.osquery.packs.table.activeColumnTitle', { defaultMessage: 'Active', }), @@ -221,7 +218,7 @@ const PacksTableComponent = () => { const sorting = useMemo( () => ({ sort: { - field: 'attributes.name', + field: 'name', direction: 'asc' as const, }, }), diff --git a/x-pack/plugins/osquery/public/packs/types.ts b/x-pack/plugins/osquery/public/packs/types.ts index b5d5181f98d039..071795c1d966f9 100644 --- a/x-pack/plugins/osquery/public/packs/types.ts +++ b/x-pack/plugins/osquery/public/packs/types.ts @@ -4,11 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { SavedObject } from '@kbn/core/public'; import type { Shard } from '../../common/schemas/common/utils'; import type { PackQueryFormData } from './queries/use_pack_query_form'; -export type PackSavedObject = SavedObject<{ +export interface PackSavedObject { + saved_object_id: string; name: string; description: string | undefined; queries: Record>; @@ -18,9 +18,10 @@ export type PackSavedObject = SavedObject<{ created_by: string | undefined; updated_at: string; updated_by: string | undefined; -}>; + references: Array<{ name: string; id: string; type: string }>; +} -export type PackItem = PackSavedObject['attributes'] & { +export type PackItem = PackSavedObject & { id: string; policy_ids: string[]; read_only?: boolean; diff --git a/x-pack/plugins/osquery/public/packs/use_create_pack.ts b/x-pack/plugins/osquery/public/packs/use_create_pack.ts index 578e80c7234a27..9c81f71e441f2f 100644 --- a/x-pack/plugins/osquery/public/packs/use_create_pack.ts +++ b/x-pack/plugins/osquery/public/packs/use_create_pack.ts @@ -51,7 +51,7 @@ export const useCreatePack = ({ withRedirect }: UseCreatePackProps) => { i18n.translate('xpack.osquery.newPack.successToastMessageText', { defaultMessage: 'Successfully created "{packName}" pack', values: { - packName: payload.data.attributes?.name ?? '', + packName: payload.data?.name ?? '', }, }) ); diff --git a/x-pack/plugins/osquery/public/packs/use_packs.ts b/x-pack/plugins/osquery/public/packs/use_packs.ts index c2bd71dfd0676c..afe612094f6603 100644 --- a/x-pack/plugins/osquery/public/packs/use_packs.ts +++ b/x-pack/plugins/osquery/public/packs/use_packs.ts @@ -5,16 +5,16 @@ * 2.0. */ -import type { SavedObjectsFindResponse } from '@kbn/core/public'; import { useQuery } from '@tanstack/react-query'; import { useKibana } from '../common/lib/kibana'; import { PACKS_ID } from './constants'; import type { PackSavedObject } from './types'; -export type UsePacksResponse = Omit & { +export interface UsePacksResponse { + total: number; data: PackSavedObject[]; -}; +} export const usePacks = ({ isLive = false, diff --git a/x-pack/plugins/osquery/public/packs/use_update_pack.ts b/x-pack/plugins/osquery/public/packs/use_update_pack.ts index 840d01e9b32fdf..c5d7fc8b432105 100644 --- a/x-pack/plugins/osquery/public/packs/use_update_pack.ts +++ b/x-pack/plugins/osquery/public/packs/use_update_pack.ts @@ -21,7 +21,7 @@ interface UseUpdatePackProps { options?: UseMutationOptions< { data: PackSavedObject }, { body: { message: string; error: string } }, - Partial & { id: string } + Partial & { id: string } >; } @@ -37,7 +37,7 @@ export const useUpdatePack = ({ withRedirect, options }: UseUpdatePackProps) => return useMutation< { data: PackSavedObject }, { body: { message: string; error: string } }, - Partial & { id: string } + Partial & { id: string } >( ({ id, ...payload }) => http.put(`/api/osquery/packs/${id}`, { @@ -57,7 +57,7 @@ export const useUpdatePack = ({ withRedirect, options }: UseUpdatePackProps) => i18n.translate('xpack.osquery.updatePack.successToastMessageText', { defaultMessage: 'Successfully updated "{packName}" pack', values: { - packName: response?.data?.attributes?.name ?? '', + packName: response?.data?.name ?? '', }, }) ); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx index 0dbf921997ede4..c6f9f9bb3b26d2 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/index.tsx @@ -41,12 +41,9 @@ const EditSavedQueryPageComponent = () => { const updateSavedQueryMutation = useUpdateSavedQuery({ savedQueryId }); const deleteSavedQueryMutation = useDeleteSavedQuery({ savedQueryId }); - useBreadcrumbs('saved_query_edit', { savedQueryName: savedQueryDetails?.attributes?.id ?? '' }); + useBreadcrumbs('saved_query_edit', { savedQueryName: savedQueryDetails?.saved_object_id ?? '' }); - const elasticPrebuiltQuery = useMemo( - () => !!savedQueryDetails?.attributes?.prebuilt, - [savedQueryDetails] - ); + const elasticPrebuiltQuery = useMemo(() => !!savedQueryDetails?.prebuilt, [savedQueryDetails]); const viewMode = useMemo( () => !permissions.writeSavedQueries || elasticPrebuiltQuery, [permissions.writeSavedQueries, elasticPrebuiltQuery] @@ -87,7 +84,7 @@ const EditSavedQueryPageComponent = () => { defaultMessage='"{savedQueryId}" details' // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop values={{ - savedQueryId: savedQueryDetails?.attributes?.id ?? '', + savedQueryId: savedQueryDetails?.id ?? '', }} /> {elasticPrebuiltQuery && ( @@ -105,7 +102,7 @@ const EditSavedQueryPageComponent = () => { defaultMessage='Edit "{savedQueryId}"' // eslint-disable-next-line react-perf/jsx-no-new-object-as-prop values={{ - savedQueryId: savedQueryDetails?.attributes?.id ?? '', + savedQueryId: savedQueryDetails?.id ?? '', }} /> )} @@ -114,7 +111,7 @@ const EditSavedQueryPageComponent = () => { ), - [elasticPrebuiltQuery, savedQueryDetails?.attributes?.id, savedQueryListProps, viewMode] + [elasticPrebuiltQuery, savedQueryDetails?.id, savedQueryListProps, viewMode] ); const RightColumn = useMemo( @@ -146,7 +143,7 @@ const EditSavedQueryPageComponent = () => { > {!isLoading && !isEmpty(savedQueryDetails) && ( diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx index 276f2f2598d1eb..767e323fa52544 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx @@ -21,8 +21,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useHistory } from 'react-router-dom'; import deepEqual from 'fast-deep-equal'; - -import type { SavedObject } from '@kbn/core/public'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { Direction } from '../../../../common/search_strategy'; import { WithHeaderLayout } from '../../../components/layouts'; @@ -30,15 +28,16 @@ import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs'; import { useKibana, useRouterNavigate } from '../../../common/lib/kibana'; import { useSavedQueries } from '../../../saved_queries/use_saved_queries'; -export type SavedQuerySO = SavedObject<{ +export interface SavedQuerySO { name: string; id: string; + saved_object_id: string; description?: string; query: string; ecs_mapping: ECSMapping; updated_at: string; prebuilt?: boolean; -}>; +} interface PlayButtonProps { disabled: boolean; @@ -54,8 +53,8 @@ const PlayButtonComponent: React.FC = ({ disabled = false, save push('/live_queries/new', { form: { savedQueryId: savedQuery.id, - query: savedQuery.attributes.query, - ecs_mapping: savedQuery.attributes.ecs_mapping, + query: savedQuery.query, + ecs_mapping: savedQuery.ecs_mapping, }, }), [push, savedQuery] @@ -66,7 +65,7 @@ const PlayButtonComponent: React.FC = ({ disabled = false, save i18n.translate('xpack.osquery.savedQueryList.queriesTable.runActionAriaLabel', { defaultMessage: 'Run {savedQueryName}', values: { - savedQueryName: savedQuery.attributes.id, + savedQueryName: savedQuery.id, }, }), [savedQuery] @@ -133,14 +132,14 @@ const SavedQueriesPageComponent = () => { const newQueryLinkProps = useRouterNavigate('saved_queries/new'); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(20); - const [sortField, setSortField] = useState('attributes.updated_at'); + const [sortField, setSortField] = useState('updated_at'); const [sortDirection, setSortDirection] = useState(Direction.desc); const { data } = useSavedQueries({ isLive: true }); const renderEditAction = useCallback( (item: SavedQuerySO) => ( - + ), [] ); @@ -158,10 +157,7 @@ const SavedQueriesPageComponent = () => { const renderUpdatedAt = useCallback((updatedAt, item) => { if (!updatedAt) return '-'; - const updatedBy = - item.attributes.updated_by !== item.attributes.created_by - ? ` @ ${item.attributes.updated_by}` - : ''; + const updatedBy = item.updated_by !== item.created_by ? ` @ ${item.updated_by}` : ''; return updatedAt ? `${moment(updatedAt).fromNow()}${updatedBy}` : '-'; }, []); @@ -179,16 +175,16 @@ const SavedQueriesPageComponent = () => { const columns: Array> = useMemo( () => [ { - field: 'attributes.id', + field: 'id', name: i18n.translate('xpack.osquery.savedQueries.table.queryIdColumnTitle', { defaultMessage: 'Query ID', }), - sortable: (item) => item.attributes.id.toLowerCase(), + sortable: (item) => item.id.toLowerCase(), truncateText: true, width: '15%', }, { - field: 'attributes.description', + field: 'description', name: i18n.translate('xpack.osquery.savedQueries.table.descriptionColumnTitle', { defaultMessage: 'Description', }), @@ -196,7 +192,7 @@ const SavedQueriesPageComponent = () => { width: '50%', }, { - field: 'attributes.created_by', + field: 'created_by', name: i18n.translate('xpack.osquery.savedQueries.table.createdByColumnTitle', { defaultMessage: 'Created by', }), @@ -205,13 +201,12 @@ const SavedQueriesPageComponent = () => { truncateText: true, }, { - field: 'attributes.updated_at', + field: 'updated_at', name: i18n.translate('xpack.osquery.savedQueries.table.updatedAtColumnTitle', { defaultMessage: 'Last updated at', }), width: '10%', - sortable: (item) => - item.attributes.updated_at ? Date.parse(item.attributes.updated_at) : 0, + sortable: (item) => (item.updated_at ? Date.parse(item.updated_at) : 0), truncateText: true, render: renderUpdatedAt, }, diff --git a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx index 255983ab0632d1..453fb9b87c6cd0 100644 --- a/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/form/use_saved_query_form.tsx @@ -77,7 +77,7 @@ export const savedQueryDataSerializer = (payload: SavedQueryFormData): SavedQuer export const useSavedQueryForm = ({ defaultValue }: UseSavedQueryFormProps) => { const { data } = useSavedQueries({}); - const ids: string[] = useMemo(() => map(data?.data, 'attributes.id') ?? [], [data]); + const ids: string[] = useMemo(() => map(data?.data, 'id') ?? [], [data]); const idSet = useMemo>(() => { const res = new Set(ids); if (defaultValue && defaultValue.id) res.delete(defaultValue.id); diff --git a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx index 139ab77a557815..1e49a06ca57490 100644 --- a/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx +++ b/x-pack/plugins/osquery/public/saved_queries/saved_queries_dropdown.tsx @@ -31,7 +31,7 @@ export interface SavedQueriesDropdownProps { disabled?: boolean; onChange: ( value: - | (Pick & { + | (Pick & { savedQueryId: string; }) | null @@ -40,7 +40,7 @@ export interface SavedQueriesDropdownProps { interface SelectedOption { label: string; - value: Pick & { + value: Pick & { savedQueryId: string; }; } @@ -60,13 +60,13 @@ const SavedQueriesDropdownComponent: React.FC = ({ const queryOptions = useMemo( () => data?.data?.map((savedQuery) => ({ - label: savedQuery.attributes.id ?? '', + label: savedQuery.id ?? '', value: { savedQueryId: savedQuery.id, - id: savedQuery.attributes.id, - description: savedQuery.attributes.description, - query: savedQuery.attributes.query, - ecs_mapping: savedQuery.attributes.ecs_mapping, + id: savedQuery.id, + description: savedQuery.description, + query: savedQuery.query, + ecs_mapping: savedQuery.ecs_mapping, }, })) ?? [], [data] @@ -81,13 +81,10 @@ const SavedQueriesDropdownComponent: React.FC = ({ return; } - const selectedSavedQuery = find( - ['attributes.id', newSelectedOptions[0].value.id], - data?.data - ); + const selectedSavedQuery = find(['id', newSelectedOptions[0].value.id], data?.data); if (selectedSavedQuery) { - onChange({ ...selectedSavedQuery.attributes, savedQueryId: selectedSavedQuery.id }); + onChange({ ...selectedSavedQuery, savedQueryId: selectedSavedQuery.id }); } setSelectedOptions(newSelectedOptions); diff --git a/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts index e4d4619beeee0a..ce79fb25ace3e0 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_create_saved_query.ts @@ -55,7 +55,7 @@ export const useCreateSavedQuery = ({ withRedirect }: UseCreateSavedQueryProps) i18n.translate('xpack.osquery.newSavedQuery.successToastMessageText', { defaultMessage: 'Successfully saved "{savedQueryId}" query', values: { - savedQueryId: response.data.attributes?.id ?? '', + savedQueryId: response.data?.id ?? '', }, }) ); diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts index bceeaed5be435e..f56bd9c0ec010a 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts @@ -7,7 +7,6 @@ import { useQuery } from '@tanstack/react-query'; -import type { SavedObjectsFindResponse } from '@kbn/core/public'; import { useKibana } from '../common/lib/kibana'; import { useErrorToast } from '../common/hooks/use_error_toast'; import { SAVED_QUERIES_ID } from './constants'; @@ -24,7 +23,10 @@ export const useSavedQueries = ({ const setErrorToast = useErrorToast(); return useQuery< - Omit & { + { + total: number; + perPage: number; + page: number; data: SavedQuerySO[]; }, { body: { error: string; message: string } } diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts index e5c39d76c94f29..96192c2b7abdc7 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts @@ -33,7 +33,7 @@ export const useSavedQuery = ({ savedQueryId }: UseSavedQueryProps) => { }; }, { body: { error: string; message: string } }, - SavedQuerySO + SavedQuerySO & { error?: { error: string; message: string } } >( [SAVED_QUERY_ID, { savedQueryId }], () => http.get(`/api/osquery/saved_queries/${savedQueryId}`), diff --git a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts index aef48b7577ecef..58fcc06740c459 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_update_saved_query.ts @@ -48,7 +48,7 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps) i18n.translate('xpack.osquery.editSavedQuery.successToastMessageText', { defaultMessage: 'Successfully updated "{savedQueryName}" query', values: { - savedQueryName: payload.data.attributes?.id ?? '', + savedQueryName: payload.data.id ?? '', }, }) ); diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx index 4cb2d3d401a2de..4942439844bf78 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/pack_field_wrapper.tsx @@ -37,7 +37,7 @@ export const PackFieldWrapper = ({ const { packId } = useWatch<{ packId: string[] }>(); const selectedPackData = useMemo( - () => (packId?.length ? find(packsData?.data, { id: packId[0] }) : null), + () => (packId?.length ? find(packsData?.data, { saved_object_id: packId[0] }) : null), [packId, packsData] ); @@ -56,18 +56,16 @@ export const PackFieldWrapper = ({ {submitButtonContent} - {liveQueryDetails?.queries?.length || selectedPackData?.attributes?.queries?.length ? ( - <> - - - - + {liveQueryDetails?.queries?.length || selectedPackData?.queries?.length ? ( + + + ) : null} ); diff --git a/x-pack/plugins/osquery/server/common/types.ts b/x-pack/plugins/osquery/server/common/types.ts index 51dc4f59ed5b4b..baaacb256e8b68 100644 --- a/x-pack/plugins/osquery/server/common/types.ts +++ b/x-pack/plugins/osquery/server/common/types.ts @@ -5,18 +5,15 @@ * 2.0. */ -import type { SavedObject } from '@kbn/core/server'; - export interface IQueryPayload { - attributes?: { - name: string; - id: string; - }; + name: string; + id: string; } export type SOShard = Array<{ key: string; value: number }>; -export interface PackSavedObjectAttributes { +export interface PackSavedObject { + saved_object_id: string; name: string; description: string | undefined; queries: Array<{ @@ -36,11 +33,10 @@ export interface PackSavedObjectAttributes { updated_by: string | undefined; policy_ids?: string[]; shards: SOShard; + references: Array<{ name: string; type: string; id: string }>; } -export type PackSavedObject = SavedObject; - -export interface SavedQuerySavedObjectAttributes { +export interface SavedQuerySavedObject { id: string; description: string | undefined; query: string; @@ -55,8 +51,6 @@ export interface SavedQuerySavedObjectAttributes { updated_by: string | undefined; } -export type SavedQuerySavedObject = SavedObject; - export interface HTTPError extends Error { statusCode: number; } diff --git a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts index 169ac4291a9df2..4a38cf383c6436 100644 --- a/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts +++ b/x-pack/plugins/osquery/server/handlers/action/create_action_handler.ts @@ -20,7 +20,7 @@ import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/r import { convertSOQueriesToPack } from '../../routes/pack/utils'; import { ACTIONS_INDEX } from '../../../common/constants'; import { TELEMETRY_EBT_LIVE_QUERY_EVENT } from '../../lib/telemetry/constants'; -import type { PackSavedObjectAttributes } from '../../common/types'; +import type { PackSavedObject } from '../../common/types'; import { CustomHttpRequestError } from '../../common/error'; interface Metadata { @@ -63,10 +63,7 @@ export const createActionHandler = async ( let packSO; if (params.pack_id) { - packSO = await savedObjectsClient.get( - packSavedObjectType, - params.pack_id - ); + packSO = await savedObjectsClient.get(packSavedObjectType, params.pack_id); } const osqueryAction = { diff --git a/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts b/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts index 0754ebc74bd5fe..b8e8d99ed99cad 100644 --- a/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts +++ b/x-pack/plugins/osquery/server/lib/telemetry/helpers.ts @@ -6,13 +6,9 @@ */ import { filter, find, isEmpty, pick, isString } from 'lodash'; -import type { SavedObjectsFindResponse } from '@kbn/core/server'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; -import type { - PackSavedObjectAttributes, - SavedQuerySavedObjectAttributes, -} from '../../common/types'; +import type { PackSavedObject, SavedQuerySavedObject } from '../../common/types'; /** * Constructs the configs telemetry schema from a collection of config saved objects @@ -28,21 +24,18 @@ export const templateConfigs = (configsData: PackagePolicy[]) => /** * Constructs the packs telemetry schema from a collection of packs saved objects */ -export const templatePacks = ( - packsData: SavedObjectsFindResponse['saved_objects'] -) => { - const nonEmptyQueryPacks = filter(packsData, (pack) => !isEmpty(pack.attributes.queries)); +export const templatePacks = (packsData: PackSavedObject[]) => { + const nonEmptyQueryPacks = filter(packsData, (pack) => !isEmpty(pack.queries)); return nonEmptyQueryPacks.map((item) => pick( { - name: item.attributes.name, - enabled: item.attributes.enabled, - queries: item.attributes.queries, + name: item.name, + enabled: item.enabled, + queries: item.queries, policies: (filter(item.references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id')?.length, prebuilt: - !!filter(item.references, ['type', 'osquery-pack-asset']) && - item.attributes.version !== undefined, + !!filter(item.references, ['type', 'osquery-pack-asset']) && item.version !== undefined, }, ['name', 'queries', 'policies', 'prebuilt', 'enabled'] ) @@ -53,18 +46,16 @@ export const templatePacks = ( * Constructs the packs telemetry schema from a collection of packs saved objects */ export const templateSavedQueries = ( - savedQueriesData: SavedObjectsFindResponse['saved_objects'], + savedQueriesData: SavedQuerySavedObject[], prebuiltSavedQueryIds: string[] ) => savedQueriesData.map((item) => ({ - id: item.attributes.id, - query: item.attributes.query, - platform: item.attributes.platform, - interval: isString(item.attributes.interval) - ? parseInt(item.attributes.interval, 10) - : item.attributes.interval, - ...(!isEmpty(item.attributes.snapshot) ? { snapshot: item.attributes.snapshot } : {}), - ...(!isEmpty(item.attributes.removed) ? { snapshot: item.attributes.removed } : {}), - ...(!isEmpty(item.attributes.ecs_mapping) ? { ecs_mapping: item.attributes.ecs_mapping } : {}), + id: item.id, + query: item.query, + platform: item.platform, + interval: isString(item.interval) ? parseInt(item.interval, 10) : item.interval, + ...(!isEmpty(item.snapshot) ? { snapshot: item.snapshot } : {}), + ...(!isEmpty(item.removed) ? { snapshot: item.removed } : {}), + ...(!isEmpty(item.ecs_mapping) ? { ecs_mapping: item.ecs_mapping } : {}), prebuilt: prebuiltSavedQueryIds.includes(item.id), })); diff --git a/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts b/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts index 9ce8de70ab5df4..0053cd95231359 100644 --- a/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/osquery/server/lib/telemetry/receiver.ts @@ -22,10 +22,7 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { OSQUERY_INTEGRATION_NAME } from '../../../common'; import { packSavedObjectType, savedQuerySavedObjectType } from '../../../common/types'; import type { OsqueryAppContextService } from '../osquery_app_context_services'; -import type { - PackSavedObjectAttributes, - SavedQuerySavedObjectAttributes, -} from '../../common/types'; +import type { PackSavedObject, SavedQuerySavedObject } from '../../common/types'; import { getPrebuiltSavedQueryIds } from '../../routes/saved_query/utils'; export class TelemetryReceiver { @@ -54,23 +51,39 @@ export class TelemetryReceiver { } public async fetchPacks() { - return this.soClient?.find({ - type: packSavedObjectType, - page: 1, - perPage: this.max_records, - sortField: 'updated_at', - sortOrder: 'desc', - }); + return this.soClient + ?.find({ + type: packSavedObjectType, + page: 1, + perPage: this.max_records, + sortField: 'updated_at', + sortOrder: 'desc', + }) + .then((data) => ({ + ...data, + saved_objects: data.saved_objects.map((pack) => ({ + ...pack.attributes, + saved_object_id: pack.id, + })), + })); } public async fetchSavedQueries() { - return this.soClient?.find({ - type: savedQuerySavedObjectType, - page: 1, - perPage: this.max_records, - sortField: 'updated_at', - sortOrder: 'desc', - }); + return this.soClient + ?.find({ + type: savedQuerySavedObjectType, + page: 1, + perPage: this.max_records, + sortField: 'updated_at', + sortOrder: 'desc', + }) + .then((data) => ({ + ...data, + saved_objects: data.saved_objects.map((savedQuery) => ({ + ...savedQuery.attributes, + saved_object_id: savedQuery.id, + })), + })); } public async fetchConfigs() { diff --git a/x-pack/plugins/osquery/server/lib/update_global_packs.ts b/x-pack/plugins/osquery/server/lib/update_global_packs.ts index 1f806c06f79779..8a3e0485322022 100644 --- a/x-pack/plugins/osquery/server/lib/update_global_packs.ts +++ b/x-pack/plugins/osquery/server/lib/update_global_packs.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedObjectsClient, SavedObjectsFindResponse } from '@kbn/core/server'; +import type { SavedObjectsClient } from '@kbn/core/server'; import { set } from '@kbn/safer-lodash-set'; import { has, map, mapKeys } from 'lodash'; import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; @@ -14,14 +14,13 @@ import produce from 'immer'; import { convertShardsToObject } from '../routes/utils'; import { packSavedObjectType } from '../../common/types'; import type { OsqueryAppContextService } from './osquery_app_context_services'; -import type { PackSavedObjectAttributes } from '../common/types'; -import { convertSOQueriesToPackConfig } from '../routes/pack/utils'; import type { PackSavedObject } from '../common/types'; +import { convertSOQueriesToPackConfig } from '../routes/pack/utils'; export const updateGlobalPacksCreateCallback = async ( packagePolicy: NewPackagePolicy, packsClient: SavedObjectsClient, - allPacks: SavedObjectsFindResponse, + allPacks: PackSavedObject[], osqueryContext: OsqueryAppContextService ) => { const agentPolicyService = osqueryContext.getAgentPolicyService(); @@ -35,8 +34,8 @@ export const updateGlobalPacksCreateCallback = async ( : {}; const packsContainingShardForPolicy: PackSavedObject[] = []; - allPacks.saved_objects.map((pack) => { - const shards = convertShardsToObject(pack.attributes.shards); + allPacks.map((pack) => { + const shards = convertShardsToObject(pack.shards); return map(shards, (shard, shardName) => { if (shardName === '*') { @@ -50,11 +49,11 @@ export const updateGlobalPacksCreateCallback = async ( map(packsContainingShardForPolicy, (pack) => { packsClient.update( packSavedObjectType, - pack.id, + pack.saved_object_id, {}, { references: [ - ...pack.references, + ...(pack.references ?? []), { id: packagePolicy.policy_id, name: agentPolicies[packagePolicy.policy_id]?.name, @@ -72,9 +71,9 @@ export const updateGlobalPacksCreateCallback = async ( } map(packsContainingShardForPolicy, (pack) => { - set(draft, `inputs[0].config.osquery.value.packs.${pack.attributes.name}`, { + set(draft, `inputs[0].config.osquery.value.packs.${pack.name}`, { shard: 100, - queries: convertSOQueriesToPackConfig(pack.attributes.queries), + queries: convertSOQueriesToPackConfig(pack.queries), }); }); diff --git a/x-pack/plugins/osquery/server/plugin.ts b/x-pack/plugins/osquery/server/plugin.ts index 084cb2b1ba9f5f..117feb69fa4b9d 100644 --- a/x-pack/plugins/osquery/server/plugin.ts +++ b/x-pack/plugins/osquery/server/plugin.ts @@ -19,7 +19,7 @@ import type { NewPackagePolicy, UpdatePackagePolicy } from '@kbn/fleet-plugin/co import type { Subscription } from 'rxjs'; import { upgradeIntegration } from './utils/upgrade_integration'; -import type { PackSavedObjectAttributes } from './common/types'; +import type { PackSavedObject } from './common/types'; import { updateGlobalPacksCreateCallback } from './lib/update_global_packs'; import { packSavedObjectType } from '../common/types'; import { createConfig } from './create_config'; @@ -146,15 +146,23 @@ export class OsqueryPlugin implements Plugin({ - type: packSavedObjectType, - }); + const allPacks = await client + .find({ + type: packSavedObjectType, + }) + .then((data) => ({ + ...data, + saved_objects: data.saved_objects.map((pack) => ({ + ...pack.attributes, + saved_object_id: pack.id, + })), + })); if (allPacks.saved_objects) { return updateGlobalPacksCreateCallback( newPackagePolicy, client, - allPacks, + allPacks.saved_objects, this.osqueryAppContextService ); } diff --git a/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts b/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts index edd32ab645e566..dbc1844cd7d636 100644 --- a/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts +++ b/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts @@ -18,7 +18,7 @@ import { combineMerge } from './utils'; import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { convertSOQueriesToPack, convertPackQueriesToSO } from '../pack/utils'; -import type { PackSavedObjectAttributes } from '../../common/types'; +import type { PackSavedObject } from '../../common/types'; export const updateAssetsRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.post( @@ -92,7 +92,7 @@ export const updateAssetsRoute = (router: IRouter, osqueryContext: OsqueryAppCon await Promise.all([ ...install.map(async (installationPackAsset) => { - const packAssetSavedObject = await savedObjectsClient.get( + const packAssetSavedObject = await savedObjectsClient.get( installationPackAsset.type, installationPackAsset.id ); @@ -138,19 +138,18 @@ export const updateAssetsRoute = (router: IRouter, osqueryContext: OsqueryAppCon ); }), ...update.map(async (updatePackAsset) => { - const packAssetSavedObject = await savedObjectsClient.get( + const packAssetSavedObject = await savedObjectsClient.get( updatePackAsset.type, updatePackAsset.id ); - const packSavedObjectsResponse = - await savedObjectsClient.find({ - type: 'osquery-pack', - hasReference: { - type: updatePackAsset.type, - id: updatePackAsset.id, - }, - }); + const packSavedObjectsResponse = await savedObjectsClient.find({ + type: 'osquery-pack', + hasReference: { + type: updatePackAsset.type, + id: updatePackAsset.id, + }, + }); if (packSavedObjectsResponse.total) { await savedObjectsClient.update( diff --git a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts index ff4ffe5b28287d..7b51361ad5740a 100644 --- a/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts @@ -27,7 +27,9 @@ import { getInitialPolicies, } from './utils'; import { convertShardsToArray, getInternalSavedObjectsClient } from '../utils'; -import type { PackSavedObjectAttributes } from '../../common/types'; +import type { PackSavedObject } from '../../common/types'; + +type PackSavedObjectLimited = Omit; export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.post( @@ -121,7 +123,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte type: AGENT_POLICY_SAVED_OBJECT_TYPE, })); - const packSO = await savedObjectsClient.create( + const packSO = await savedObjectsClient.create( packSavedObjectType, { name, @@ -174,7 +176,7 @@ export const createPackRoute = (router: IRouter, osqueryContext: OsqueryAppConte return response.ok({ body: { - data: packSO, + data: { ...packSO.attributes, saved_object_id: packSO.id }, }, }); } diff --git a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts index 07457597e9baf0..a4ffa4db0297d8 100644 --- a/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/find_pack_route.ts @@ -12,7 +12,7 @@ import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; import { packSavedObjectType } from '../../../common/types'; import { PLUGIN_ID } from '../../../common'; -import type { PackSavedObjectAttributes } from '../../common/types'; +import type { PackSavedObject } from '../../common/types'; export const findPackRoute = (router: IRouter) => { router.get( @@ -35,7 +35,7 @@ export const findPackRoute = (router: IRouter) => { const coreContext = await context.core; const savedObjectsClient = coreContext.savedObjects.client; - const soClientResponse = await savedObjectsClient.find({ + const soClientResponse = await savedObjectsClient.find({ type: packSavedObjectType, page: request.query.page ?? 1, perPage: request.query.pageSize ?? 20, @@ -50,7 +50,8 @@ export const findPackRoute = (router: IRouter) => { ); return { - ...pack, + ...pack.attributes, + saved_object_id: pack.id, policy_ids: policyIds, }; }); diff --git a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts index 7dbea0f1972472..c16150eafc0ff0 100644 --- a/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts @@ -9,7 +9,7 @@ import { filter, map } from 'lodash'; import { schema } from '@kbn/config-schema'; import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { IRouter } from '@kbn/core/server'; -import type { PackSavedObjectAttributes } from '../../common/types'; +import type { PackSavedObject } from '../../common/types'; import { PLUGIN_ID } from '../../../common'; import { packSavedObjectType } from '../../../common/types'; @@ -31,11 +31,10 @@ export const readPackRoute = (router: IRouter) => { const coreContext = await context.core; const savedObjectsClient = coreContext.savedObjects.client; - const { attributes, references, ...rest } = - await savedObjectsClient.get( - packSavedObjectType, - request.params.id - ); + const { attributes, references, id, ...rest } = await savedObjectsClient.get( + packSavedObjectType, + request.params.id + ); const policyIds = map(filter(references, ['type', AGENT_POLICY_SAVED_OBJECT_TYPE]), 'id'); const osqueryPackAssetReference = !!filter(references, ['type', 'osquery-pack-asset']); @@ -45,6 +44,7 @@ export const readPackRoute = (router: IRouter) => { data: { ...rest, ...attributes, + saved_object_id: id, queries: convertSOQueriesToPack(attributes.queries), shards: convertShardsToObject(attributes.shards), policy_ids: policyIds, diff --git a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts index 76c7a56beeeac9..1dfa4e897455f9 100644 --- a/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts +++ b/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts @@ -30,7 +30,7 @@ import { } from './utils'; import { convertShardsToArray, getInternalSavedObjectsClient } from '../utils'; -import type { PackSavedObjectAttributes } from '../../common/types'; +import type { PackSavedObject } from '../../common/types'; export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.put( @@ -100,7 +100,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte ); if (name) { - const conflictingEntries = await savedObjectsClient.find({ + const conflictingEntries = await savedObjectsClient.find({ type: packSavedObjectType, filter: `${packSavedObjectType}.attributes.name: "${name}"`, }); @@ -159,7 +159,7 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte const references = getUpdatedReferences(); - await savedObjectsClient.update( + await savedObjectsClient.update( packSavedObjectType, request.params.id, { @@ -349,7 +349,9 @@ export const updatePackRoute = (router: IRouter, osqueryContext: OsqueryAppConte ); } - return response.ok({ body: { data: updatedPackSO } }); + return response.ok({ + body: { data: { ...updatedPackSO.attributes, saved_object_id: updatedPackSO.id } }, + }); } ); }; diff --git a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts index 4ad6146328a1ca..c65d300d124d11 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts @@ -84,7 +84,8 @@ export const createSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp body: { data: pickBy( { - ...savedQuerySO, + ...savedQuerySO.attributes, + saved_object_id: savedQuerySO.id, ecs_mapping, }, (value) => !isEmpty(value) diff --git a/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts index 79ec614b443d31..e4f06d8fd536bd 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/find_saved_query_route.ts @@ -61,7 +61,10 @@ export const findSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC savedObject.attributes.ecs_mapping = convertECSMappingToObject(ecs_mapping); } - return savedObject; + return { + ...savedObject.attributes, + saved_object_id: savedObject.id, + }; }); return response.ok({ diff --git a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts index abb897768b8a53..63f72e11b69d99 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/read_saved_query_route.ts @@ -46,7 +46,7 @@ export const readSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAppC ); return response.ok({ - body: { data: savedQuery }, + body: { data: { ...savedQuery.attributes, saved_object_id: savedQuery.id } }, }); } ); diff --git a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts index b83787794c2b7d..c990366327b548 100644 --- a/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts @@ -123,7 +123,9 @@ export const updateSavedQueryRoute = (router: IRouter, osqueryContext: OsqueryAp } return response.ok({ - body: { data: updatedSavedQuerySO }, + body: { + data: { ...updatedSavedQuerySO.attributes, saved_object_id: updatedSavedQuerySO.id }, + }, }); } ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts index e5777f97253832..57ef1f89747086 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts @@ -9,7 +9,6 @@ import * as t from 'io-ts'; import type { CreateRuleExceptionListItemSchemaDecoded } from '@kbn/securitysolution-io-ts-list-types'; import { createRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { RequiredKeepUndefined } from '@kbn/osquery-plugin/common/types'; import { RuleObjectId } from '../../../rule_schema'; @@ -39,7 +38,7 @@ export const CreateRuleExceptionsRequestBody = t.exact( * This type is used after a decode since some things are defaults after a decode. */ export type CreateRuleExceptionsRequestBodyDecoded = Omit< - RequiredKeepUndefined, + CreateRuleExceptionsRequestBody, 'items' > & { items: CreateRuleExceptionListItemSchemaDecoded[]; diff --git a/x-pack/plugins/security_solution/cypress/.eslintrc.json b/x-pack/plugins/security_solution/cypress/.eslintrc.json index a738652e2d27b1..22a4d052afdc5d 100644 --- a/x-pack/plugins/security_solution/cypress/.eslintrc.json +++ b/x-pack/plugins/security_solution/cypress/.eslintrc.json @@ -1,9 +1,13 @@ { "plugins": ["cypress"], + "extends": [ + "plugin:cypress/recommended" + ], "env": { "cypress/globals": true }, "rules": { + "cypress/no-force": "warn", "import/no-extraneous-dependencies": "off" } } diff --git a/x-pack/plugins/security_solution/cypress/README.md b/x-pack/plugins/security_solution/cypress/README.md index 0c32cda3f0245b..a2ff1e2f38da8b 100644 --- a/x-pack/plugins/security_solution/cypress/README.md +++ b/x-pack/plugins/security_solution/cypress/README.md @@ -440,9 +440,6 @@ taken into consideration until another solution is implemented: Remember that minimizing the number of times the web page is loaded, we minimize as well the execution time. -### Cypress-pipe -It is very common in the code to don't have click handlers regitered. In this specific case, please use [Cypress pipe](https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/). - ### CCS test specific When testing CCS we want to put our focus in making sure that our `Source` instance is receiving properly the data that comes from the `Remote` instances, as well as the data is displayed as we expect on the `Source`. diff --git a/x-pack/plugins/security_solution/cypress/cypress.config.ts b/x-pack/plugins/security_solution/cypress/cypress.config.ts index d831f839a63a4b..2a945fc1ab012d 100644 --- a/x-pack/plugins/security_solution/cypress/cypress.config.ts +++ b/x-pack/plugins/security_solution/cypress/cypress.config.ts @@ -11,13 +11,16 @@ export default defineCypressConfig({ defaultCommandTimeout: 60000, execTimeout: 60000, pageLoadTimeout: 60000, + responseTimeout: 60000, screenshotsFolder: '../../../target/kibana-security-solution/cypress/screenshots', trashAssetsBeforeRuns: false, video: false, videosFolder: '../../../target/kibana-security-solution/cypress/videos', viewportHeight: 946, viewportWidth: 1680, + numTestsKeptInMemory: 10, e2e: { - baseUrl: 'http://localhost:5601', + experimentalRunAllSpecs: true, + experimentalMemoryManagement: true, }, }); diff --git a/x-pack/plugins/security_solution/cypress/cypress_ci.config.ts b/x-pack/plugins/security_solution/cypress/cypress_ci.config.ts index 092bef59070168..107736e45759ff 100644 --- a/x-pack/plugins/security_solution/cypress/cypress_ci.config.ts +++ b/x-pack/plugins/security_solution/cypress/cypress_ci.config.ts @@ -14,7 +14,7 @@ export default defineCypressConfig({ pageLoadTimeout: 150000, numTestsKeptInMemory: 0, retries: { - runMode: 2, + runMode: 1, }, screenshotsFolder: '../../../target/kibana-security-solution/cypress/screenshots', trashAssetsBeforeRuns: false, @@ -24,5 +24,7 @@ export default defineCypressConfig({ viewportWidth: 1680, e2e: { baseUrl: 'http://localhost:5601', + experimentalMemoryManagement: true, + specPattern: './cypress/e2e/**/*.cy.ts', }, }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/attach_alert_to_case.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/attach_alert_to_case.cy.ts index 32f5c894ded469..3e40d12177a233 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/attach_alert_to_case.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/attach_alert_to_case.cy.ts @@ -19,6 +19,7 @@ import { ATTACH_ALERT_TO_CASE_BUTTON, ATTACH_TO_NEW_CASE_BUTTON } from '../../sc import { LOADING_INDICATOR } from '../../screens/security_header'; const loadDetectionsPage = (role: ROLES) => { + login(role); waitForPageWithoutDateRange(ALERTS_URL, role); waitForAlertsToPopulate(); }; @@ -35,7 +36,6 @@ describe('Alerts timeline', () => { context('Privileges: read only', () => { beforeEach(() => { - login(ROLES.reader); loadDetectionsPage(ROLES.reader); }); @@ -54,7 +54,6 @@ describe('Alerts timeline', () => { context('Privileges: can crud', () => { beforeEach(() => { - login(ROLES.platform_engineer); loadDetectionsPage(ROLES.platform_engineer); cy.get(LOADING_INDICATOR).should('not.exist'); // on CI, waitForPageToBeLoaded fails because the loading icon can't be found }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/attach_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/attach_timeline.cy.ts index 2012d756d63545..5ef3795377e1d4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/attach_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/attach_timeline.cy.ts @@ -23,9 +23,10 @@ describe('attach timeline to case', () => { context('without cases created', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { + login(); deleteTimelines(); createTimeline(getTimeline()).then((response) => { cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline'); @@ -60,6 +61,7 @@ describe('attach timeline to case', () => { context('with cases created', () => { before(() => { + login(); deleteTimelines(); createTimeline(getTimeline()).then((response) => cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('timelineId') @@ -67,6 +69,10 @@ describe('attach timeline to case', () => { createCase(getCase1()).then((response) => cy.wrap(response.body.id).as('caseId')); }); + beforeEach(() => { + login(); + }); + it('attach timeline to an existing case', function () { visitTimeline(this.timelineId); attachTimelineToExistingCase(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts index ffcc70bee05056..5271b7fefc0932 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts @@ -33,7 +33,9 @@ describe('Cases connector incident fields', () => { cleanKibana(); login(); }); + beforeEach(() => { + login(); cy.intercept('GET', '/api/cases/configure/connectors/_find', getMockConnectorsResponse()); cy.intercept('POST', `/api/actions/connector/${getConnectorIds().sn}/_execute`, (req) => { const response = diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts index 5aa302fa61c41d..ad2d3337e9a921 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/connectors.cy.ts @@ -48,10 +48,10 @@ describe('Cases connectors', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); deleteCases(); cy.intercept('GET', `${snConnector.URL}/api/x_elas2_inc_int/elastic_api/health*`, { statusCode: 200, @@ -59,7 +59,7 @@ describe('Cases connectors', () => { }); cy.intercept('POST', '/api/actions/connector').as('createConnector'); - cy.intercept('PATCH', '/api/cases/configure/*', (req) => { + cy.intercept({ method: '+(POST|PATCH)', url: '/api/cases/configure' }, (req) => { const connector = req.body.connector; req.reply((res) => { res.send(200, { ...configureResult, connector }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/creation.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/creation.cy.ts index 260ef5a393b3ed..c8b1f9b7547c48 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/creation.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/creation.cy.ts @@ -49,7 +49,7 @@ import { fillCasesMandatoryfields, filterStatusOpen, } from '../../tasks/create_new_case'; -import { loginWithUser, visitWithoutDateRange } from '../../tasks/login'; +import { loginWithUser, visit, visitWithoutDateRange } from '../../tasks/login'; import { CASES_URL, OVERVIEW_URL } from '../../urls/navigation'; @@ -121,7 +121,7 @@ describe('Cases', () => { cy.get(TIMELINE_DESCRIPTION).contains(this.mycase.timeline.description); cy.get(TIMELINE_QUERY).should('have.text', this.mycase.timeline.query); - cy.visit(OVERVIEW_URL); + visit(OVERVIEW_URL); cy.get(OVERVIEW_CASE_NAME).should('have.text', this.mycase.name); cy.get(OVERVIEW_CASE_DESCRIPTION).should( 'have.text', diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/privileges.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/privileges.cy.ts index b19a4eb6b4501f..caf27a0f5d3360 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/privileges.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/privileges.cy.ts @@ -17,13 +17,7 @@ import { fillCasesMandatoryfields, filterStatusOpen, } from '../../tasks/create_new_case'; -import { - login, - loginWithUser, - logout, - visitHostDetailsPage, - visitWithUser, -} from '../../tasks/login'; +import { login, loginWithUser, visitWithUser } from '../../tasks/login'; import { createUsersAndRoles, deleteUsersAndRoles, @@ -38,7 +32,6 @@ import { } from '../../tasks/privileges'; import { CASES_URL } from '../../urls/navigation'; -import { openSourcerer } from '../../tasks/sourcerer'; const usersToCreate = [ secAllUser, secReadCasesAllUser, @@ -46,12 +39,6 @@ const usersToCreate = [ secAllCasesOnlyReadDeleteUser, ]; const rolesToCreate = [secAll, secReadCasesAll, secAllCasesNoDelete, secAllCasesOnlyReadDelete]; -// needed to generate index pattern -const visitSecuritySolution = () => { - visitHostDetailsPage(); - openSourcerer(); - logout(); -}; const testCase: TestCaseWithoutTimeline = { name: 'This is the title of the case', @@ -64,9 +51,7 @@ const testCase: TestCaseWithoutTimeline = { describe('Cases privileges', () => { before(() => { cleanKibana(); - login(); createUsersAndRoles(usersToCreate, rolesToCreate); - visitSecuritySolution(); }); after(() => { @@ -74,6 +59,7 @@ describe('Cases privileges', () => { }); beforeEach(() => { + login(); deleteCases(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts index b91ef7c950ac23..aeef17f1be5171 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts @@ -14,7 +14,7 @@ import { } from '../../screens/entity_analytics'; import { deleteRiskScore, - intercepInstallRiskScoreModule, + interceptInstallRiskScoreModule, waitForInstallRiskScoreModule, } from '../../tasks/api_calls/risk_scores'; import { findSavedObjects } from '../../tasks/api_calls/risk_scores/saved_objects'; @@ -40,6 +40,7 @@ describe('Enable risk scores', () => { }); beforeEach(() => { + login(); deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); visit(ENTITY_ANALYTICS_URL); @@ -55,7 +56,7 @@ describe('Enable risk scores', () => { }); it('should install host risk score successfully', () => { - intercepInstallRiskScoreModule(); + interceptInstallRiskScoreModule(); clickEnableRiskScore(RiskScoreEntity.host); waitForInstallRiskScoreModule(); @@ -89,7 +90,7 @@ describe('Enable risk scores', () => { }); it('should install user risk score successfully', () => { - intercepInstallRiskScoreModule(); + interceptInstallRiskScoreModule(); clickEnableRiskScore(RiskScoreEntity.user); waitForInstallRiskScoreModule(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts index 2c793e18dfb6be..00328b9d13bf01 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/dashboards/entity_analytics.cy.ts @@ -52,11 +52,11 @@ const END_DATE = 'Jan 19, 2019 @ 20:33:29.186'; describe('Entity Analytics Dashboard', () => { before(() => { cleanKibana(); - login(); }); describe('Without data', () => { beforeEach(() => { + login(); visit(ENTITY_ANALYTICS_URL); }); @@ -76,6 +76,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ENTITY_ANALYTICS_URL); }); @@ -100,6 +101,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ENTITY_ANALYTICS_URL); }); @@ -123,6 +125,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ENTITY_ANALYTICS_URL); }); @@ -167,6 +170,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); visit(ENTITY_ANALYTICS_URL); @@ -209,6 +213,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ENTITY_ANALYTICS_URL); }); @@ -253,6 +258,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); visit(ENTITY_ANALYTICS_URL); @@ -300,6 +306,7 @@ describe('Entity Analytics Dashboard', () => { }); beforeEach(() => { + login(); visit(ENTITY_ANALYTICS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts index a13a2790cf2d6b..09411152a6309b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts @@ -43,6 +43,7 @@ describe('Upgrade risk scores', () => { }); beforeEach(() => { + login(); deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId); @@ -87,11 +88,11 @@ versions.forEach((version) => describe(`handles version ${version} upgrades`, () => { before(() => { cleanKibana(); - login(); - createRule(getNewRule({ rule_id: 'rule1' })); }); beforeEach(() => { + login(); + createRule(getNewRule({ rule_id: 'rule1' })); deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId, version); diff --git a/x-pack/plugins/security_solution/cypress/e2e/data_sources/create_runtime_field.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/data_sources/create_runtime_field.cy.ts index a83129d638b735..bc64948a94ded8 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/data_sources/create_runtime_field.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/data_sources/create_runtime_field.cy.ts @@ -29,6 +29,9 @@ describe('Create DataView runtime field', () => { before(() => { deleteRuntimeField('security-solution-default', alertRunTimeField); deleteRuntimeField('security-solution-default', timelineRuntimeField); + }); + + beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/data_sources/sourcerer.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/data_sources/sourcerer.cy.ts index a6dfef7dc7fb84..775059078718ec 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/data_sources/sourcerer.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/data_sources/sourcerer.cy.ts @@ -59,11 +59,9 @@ describe('Sourcerer', () => { }); describe('Default scope', () => { - before(() => { - login(); - }); beforeEach(() => { cy.clearLocalStorage(); + login(); visit(HOSTS_URL); }); @@ -134,11 +132,9 @@ describe('Sourcerer', () => { }); }); describe('Timeline scope', () => { - before(() => { - login(); - }); beforeEach(() => { cy.clearLocalStorage(); + login(); visit(TIMELINES_URL); }); @@ -201,10 +197,13 @@ describe('Timeline scope', () => { cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('auditbeatTimelineId') ); }); + beforeEach(() => { + login(); visit(TIMELINES_URL); refreshUntilAlertsIndexExists(); }); + it('Modifies timeline to alerts only, and switches to different saved timeline without issue', function () { openTimelineById(this.timelineId).then(() => { cy.get(SOURCERER.badgeAlerts).should(`not.exist`); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_charts.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_charts.cy.ts index 7799b55b555515..1b883223267360 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_charts.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_charts.cy.ts @@ -24,10 +24,14 @@ import { } from '../../screens/search_bar'; import { TOASTER } from '../../screens/alerts_detection_rules'; -describe('Histogram legend hover actions', { testIsolation: false }, () => { +describe('Histogram legend hover actions', () => { const ruleConfigs = getNewRule(); + before(() => { cleanKibana(); + }); + + beforeEach(() => { login(); createRule(getNewRule({ rule_id: 'new custom rule' })); visit(ALERTS_URL); @@ -52,7 +56,7 @@ describe('Histogram legend hover actions', { testIsolation: false }, () => { ); cy.get(ALERTS_COUNT).should('not.exist'); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE).trigger('click'); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE).click(); cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('not.exist'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts index 8167f47b34e850..a091af3cc14175 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts @@ -16,6 +16,7 @@ import { createRule, deleteCustomRule } from '../../tasks/api_calls/rules'; import { getCallOut, waitForCallOutToBeShown } from '../../tasks/common/callouts'; const loadPageAsPlatformEngineerUser = (url: string) => { + login(ROLES.soc_manager); waitForPageWithoutDateRange(url, ROLES.soc_manager); waitForPageTitleToBeShown(); }; @@ -30,10 +31,9 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. - login(ROLES.platform_engineer); + login(); visitWithoutDateRange(ALERTS_URL); - // After that we can login as a soc manager. - login(ROLES.soc_manager); + waitForPageTitleToBeShown(); }); context( @@ -51,6 +51,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr }); }); }); + context('On Detections home page', () => { beforeEach(() => { loadPageAsPlatformEngineerUser(ALERTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts index d84ccc3d80a82e..2d3f2fef53dbf6 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts @@ -43,6 +43,7 @@ describe('CTI Enrichment', () => { }); beforeEach(() => { + login(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); goToRuleDetails(); }); @@ -155,6 +156,12 @@ describe('CTI Enrichment', () => { esArchiverLoad('threat_indicator2'); }); + beforeEach(() => { + login(); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); + goToRuleDetails(); + }); + after(() => { esArchiverUnload('threat_indicator2'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts index 0584fa909fccba..7a99ad0eb7e05d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts @@ -34,7 +34,6 @@ describe('Enrichment', () => { before(() => { cleanKibana(); esArchiverLoad('risk_users'); - login(); }); after(() => { @@ -46,6 +45,7 @@ describe('Enrichment', () => { esArchiverLoad('risk_hosts'); deleteAlertsAndRules(); createRule(getNewRule({ rule_id: 'rule1' })); + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts index f6fb4a904dd0a3..21b125336e7c65 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts @@ -16,11 +16,13 @@ import { createRule, deleteCustomRule } from '../../tasks/api_calls/rules'; import { getCallOut, waitForCallOutToBeShown, dismissCallOut } from '../../tasks/common/callouts'; const loadPageAsReadOnlyUser = (url: string) => { + login(ROLES.reader); waitForPageWithoutDateRange(url, ROLES.reader); waitForPageTitleToBeShown(); }; const loadPageAsPlatformEngineer = (url: string) => { + login(ROLES.platform_engineer); waitForPageWithoutDateRange(url, ROLES.platform_engineer); waitForPageTitleToBeShown(); }; @@ -40,11 +42,9 @@ describe('Detections > Callouts', () => { before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. - login(ROLES.platform_engineer); + login(); visitWithoutDateRange(ALERTS_URL); - - // After that we can login as a read-only user. - login(ROLES.reader); + waitForPageTitleToBeShown(); }); context('indicating read-only access to resources', () => { @@ -111,6 +111,7 @@ describe('Detections > Callouts', () => { context('On Rules Management page', () => { beforeEach(() => { + login(ROLES.platform_engineer); loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_detection.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_detection.cy.ts index 0ccf204423ba56..e2c449e727d291 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_detection.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_detection.cy.ts @@ -17,12 +17,12 @@ import { createTimeline } from '../../tasks/timelines'; describe('Ransomware Detection Alerts', () => { before(() => { - login(); esArchiverLoad('ransomware_detection'); }); describe('Ransomware display in Alerts Section', () => { beforeEach(() => { + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); }); @@ -46,8 +46,8 @@ describe('Ransomware Detection Alerts', () => { describe('Ransomware in Timelines', () => { before(() => { + login(); visit(TIMELINES_URL); - createTimeline(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts index 1a3920addc0432..a69afdd3a0ccc1 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/ransomware_prevention.cy.ts @@ -10,19 +10,23 @@ import { login, visit } from '../../tasks/login'; import { ALERTS_URL, TIMELINES_URL } from '../../urls/navigation'; import { ALERTS_HISTOGRAM_SERIES, ALERT_RULE_NAME, MESSAGE } from '../../screens/alerts'; -import { esArchiverLoad } from '../../tasks/es_archiver'; +import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; import { TIMELINE_QUERY, TIMELINE_VIEW_IN_ANALYZER } from '../../screens/timeline'; import { selectAlertsHistogram } from '../../tasks/alerts'; import { createTimeline } from '../../tasks/timelines'; describe('Ransomware Prevention Alerts', () => { before(() => { - login(); esArchiverLoad('ransomware_prevention'); }); + after(() => { + esArchiverUnload('ransomware_prevention'); + }); + describe('Ransomware display in Alerts Section', () => { beforeEach(() => { + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); }); @@ -45,7 +49,8 @@ describe('Ransomware Prevention Alerts', () => { }); describe('Ransomware in Timelines', () => { - before(() => { + beforeEach(() => { + login(); visit(TIMELINES_URL); createTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/all_rules_read_only.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/all_rules_read_only.cy.ts index 44fc11cfaca2dd..254990f73cb0ed 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/all_rules_read_only.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/all_rules_read_only.cy.ts @@ -26,10 +26,10 @@ describe('All rules - read only', () => { before(() => { cleanKibana(); createRule(getNewRule({ rule_id: '1' })); - login(ROLES.reader); }); beforeEach(() => { + login(ROLES.reader); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL, ROLES.reader); waitForRulesTableToBeLoaded(); cy.get(RULE_NAME).should('have.text', getNewRule().name); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts index ca25b9955bf97c..038a6c6d32853f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts @@ -53,47 +53,48 @@ const NON_EXPIRED_EXCEPTION_ITEM_NAME = 'Sample exception item with future expir describe('Detection rules, bulk duplicate', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { + login(); // Make sure persisted rules table state is cleared resetRulesTableState(); deleteAlertsAndRules(); esArchiverResetKibana(); - createRule(getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1' })).then( - (response) => { - createRuleExceptionItem(response.body.id, [ - { - description: 'Exception item for rule default exception list', - entries: [ - { - field: 'user.name', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - name: EXPIRED_EXCEPTION_ITEM_NAME, - type: 'simple', - expire_time: expiredDate, - }, - { - description: 'Exception item for rule default exception list', - entries: [ - { - field: 'user.name', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - name: NON_EXPIRED_EXCEPTION_ITEM_NAME, - type: 'simple', - expire_time: futureDate, - }, - ]); - } - ); + createRule<{ id: string }>( + getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1' }) + ).then((response) => { + createRuleExceptionItem(response.body.id, [ + { + description: 'Exception item for rule default exception list', + entries: [ + { + field: 'user.name', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + name: EXPIRED_EXCEPTION_ITEM_NAME, + type: 'simple', + expire_time: expiredDate, + }, + { + description: 'Exception item for rule default exception list', + entries: [ + { + field: 'user.name', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + name: NON_EXPIRED_EXCEPTION_ITEM_NAME, + type: 'simple', + expire_time: futureDate, + }, + ]); + }); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts index 772a1ed6e4294e..0c10a9da5d3e88 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules.cy.ts @@ -118,9 +118,9 @@ const defaultRuleData = { describe('Detection rules, bulk edit', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); // Make sure persisted rules table state is cleared resetRulesTableState(); deleteAlertsAndRules(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts index 6b308e6e258ea4..61d62902cb7024 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_data_view.cy.ts @@ -55,11 +55,12 @@ const expectedNumberOfCustomRulesToBeEdited = 6; describe('Bulk editing index patterns of rules with a data view only', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { deleteAlertsAndRules(); esArchiverResetKibana(); + login(); postDataView(DATA_VIEW_ID); @@ -179,11 +180,13 @@ describe('Bulk editing index patterns of rules with a data view only', () => { describe('Bulk editing index patterns of rules with index patterns and rules with a data view', () => { const customRulesNumber = 2; + before(() => { cleanKibana(); - login(); }); + beforeEach(() => { + login(); deleteAlertsAndRules(); esArchiverResetKibana(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts index c11969448597df..a8d81fa1bca235 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts @@ -111,8 +111,8 @@ import { RULE_CREATION, DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/naviga describe('Custom query rules', () => { before(() => { cleanKibana(); - login(); }); + describe('Custom detection rules creation', () => { const expectedNumberOfRules = 1; @@ -123,6 +123,7 @@ describe('Custom query rules', () => { return response.body.data.persistTimeline.timeline.savedObjectId; }) .as('timelineId'); + login(); }); it('Creates and enables a new rule', function () { @@ -240,6 +241,7 @@ describe('Custom query rules', () => { createRule(getNewRule({ rule_id: 'rule1', enabled: true, max_signals: 500 })); createRule(getNewOverrideRule({ rule_id: 'rule2', enabled: true, max_signals: 500 })); createRule(getExistingRule({ rule_id: 'rule3', enabled: true })); + login(); visit(DETECTIONS_RULE_MANAGEMENT_URL); }); @@ -351,7 +353,9 @@ describe('Custom query rules', () => { deleteConnectors(); createRule(getExistingRule({ rule_id: 'rule1', enabled: true })); }); + beforeEach(() => { + login(); visit(DETECTIONS_RULE_MANAGEMENT_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts index accdf1dfaa8afb..031b4e0526793c 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule_data_view.cy.ts @@ -69,10 +69,6 @@ import { getDetails } from '../../tasks/rule_details'; import { RULE_CREATION } from '../../urls/navigation'; describe('Custom query rules', () => { - before(() => { - login(); - }); - describe('Custom detection rules creation with data views', () => { const rule = getDataViewRule(); const expectedUrls = rule.references?.join(''); @@ -83,14 +79,15 @@ describe('Custom query rules', () => { const expectedNumberOfRules = 1; beforeEach(() => { - /* We don't call cleanKibana method on the before hook, instead we call esArchiverReseKibana on the before each. This is because we + /* We don't call cleanKibana method on the before hook, instead we call esArchiverReseKibana on the before each. This is because we are creating a data view we'll use after and cleanKibana does not delete all the data views created, esArchiverReseKibana does. - We don't use esArchiverReseKibana in all the tests because is a time-consuming method and we don't need to perform an exhaustive + We don't use esArchiverReseKibana in all the tests because is a time-consuming method and we don't need to perform an exhaustive cleaning in all the other tests. */ esArchiverResetKibana(); if (rule.data_view_id != null) { postDataView(rule.data_view_id); } + login(); }); it('Creates and enables a new rule', function () { @@ -150,11 +147,14 @@ describe('Custom query rules', () => { .should('match', /^[1-9].+$/); cy.get(ALERT_GRID_CELL).contains(rule.name); }); + it('Creates and edits a new rule with a data view', function () { visit(RULE_CREATION); fillDefineCustomRuleAndContinue(rule); - cy.get(RULE_NAME_INPUT).clear().type(rule.name); - cy.get(RULE_DESCRIPTION_INPUT).clear().type(rule.description); + cy.get(RULE_NAME_INPUT).clear(); + cy.get(RULE_NAME_INPUT).type(rule.name); + cy.get(RULE_DESCRIPTION_INPUT).clear(); + cy.get(RULE_DESCRIPTION_INPUT).type(rule.description); cy.get(ABOUT_CONTINUE_BTN).should('exist').click(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts index ff08bb35582e58..f9e706201d1133 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_saved_query_rule.cy.ts @@ -49,10 +49,11 @@ const savedQueryFilterKey = 'testAgent.value'; describe('Custom saved_query rules', () => { before(() => { cleanKibana(); - login(); }); + describe('Custom saved_query detection rule creation', () => { beforeEach(() => { + login(); deleteAlertsAndRules(); deleteSavedQueries(); }); @@ -102,7 +103,7 @@ describe('Custom saved_query rules', () => { const FAILED_TO_LOAD_ERROR = 'Failed to load the saved query'; beforeEach(() => { createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })); - cy.visit(SECURITY_DETECTIONS_RULES_URL); + visit(SECURITY_DETECTIONS_RULES_URL); }); it('Shows error toast on details page when saved query can not be loaded', function () { goToRuleDetails(); @@ -122,7 +123,7 @@ describe('Custom saved_query rules', () => { createSavedQuery(savedQueryName, savedQueryQuery); createRule(getNewRule()); - cy.visit(SECURITY_DETECTIONS_RULES_URL); + visit(SECURITY_DETECTIONS_RULES_URL); editFirstRule(); @@ -150,7 +151,7 @@ describe('Custom saved_query rules', () => { createRule(getSavedQueryRule({ saved_id: response.body.id, query: undefined })); }); - cy.visit(SECURITY_DETECTIONS_RULES_URL); + visit(SECURITY_DETECTIONS_RULES_URL); editFirstRule(); @@ -176,7 +177,7 @@ describe('Custom saved_query rules', () => { const expectedCustomTestQuery = 'random test query'; createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })); - cy.visit(SECURITY_DETECTIONS_RULES_URL); + visit(SECURITY_DETECTIONS_RULES_URL); editFirstRule(); @@ -199,7 +200,7 @@ describe('Custom saved_query rules', () => { createSavedQuery(savedQueryName, savedQueryQuery); createRule(getSavedQueryRule({ saved_id: 'non-existent', query: undefined })); - cy.visit(SECURITY_DETECTIONS_RULES_URL); + visit(SECURITY_DETECTIONS_RULES_URL); editFirstRule(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts index 79160b36778654..ed2ddd2b07b0e4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/event_correlation_rule.cy.ts @@ -65,9 +65,13 @@ import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; describe('EQL rules', () => { before(() => { cleanKibana(); + }); + + beforeEach(() => { login(); deleteAlertsAndRules(); }); + describe('Detection rules, EQL', () => { const rule = getEqlRule(); const expectedUrls = rule.references?.join(''); @@ -155,6 +159,7 @@ describe('EQL rules', () => { }); it('Creates and enables a new EQL rule with a sequence', function () { + login(); visit(RULE_CREATION); selectEqlRuleType(); fillDefineEqlRuleAndContinue(rule); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts index c95e5c4fbf561a..2065b914b81681 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/export_rule.cy.ts @@ -42,10 +42,10 @@ describe('Export rules', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); // Make sure persisted rules table state is cleared resetRulesTableState(); deleteAlertsAndRules(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts index 5bb0f44b0e5ecb..eaa3cc5b989703 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/import_rules.cy.ts @@ -20,9 +20,10 @@ const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson'; describe('Import rules', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { + login(); deleteAlertsAndRules(); cy.intercept('POST', '/api/detection_engine/rules/_import*').as('import'); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts index 843a6250dca2cc..329dc5ec8b4f6c 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/indicator_match_rule.cy.ts @@ -123,6 +123,9 @@ describe('indicator match', () => { cleanKibana(); esArchiverLoad('threat_indicator'); esArchiverLoad('suspicious_source_event'); + }); + + beforeEach(() => { login(); }); @@ -134,6 +137,7 @@ describe('indicator match', () => { describe('Creating new indicator match rules', () => { describe('Index patterns', () => { beforeEach(() => { + login(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); }); @@ -157,6 +161,7 @@ describe('indicator match', () => { describe('Indicator index patterns', () => { beforeEach(() => { + login(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); }); @@ -178,6 +183,7 @@ describe('indicator match', () => { describe('custom query input', () => { beforeEach(() => { + login(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); }); @@ -194,6 +200,7 @@ describe('indicator match', () => { describe('custom indicator query input', () => { beforeEach(() => { + login(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); }); @@ -210,6 +217,7 @@ describe('indicator match', () => { describe('Indicator mapping', () => { beforeEach(() => { + login(); const rule = getNewThreatIndicatorRule(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); @@ -405,6 +413,7 @@ describe('indicator match', () => { describe('Schedule', () => { it('IM rule has 1h time interval and lookback by default', () => { + login(); visitWithoutDateRange(RULE_CREATION); selectIndicatorMatchType(); fillDefineIndicatorMatchRuleAndContinue(getNewThreatIndicatorRule()); @@ -420,6 +429,7 @@ describe('indicator match', () => { describe('Generating signals', () => { beforeEach(() => { + login(); deleteAlertsAndRules(); }); @@ -530,6 +540,7 @@ describe('indicator match', () => { describe('Duplicates the indicator rule', () => { beforeEach(() => { + login(); deleteAlertsAndRules(); createRule(getNewThreatIndicatorRule({ rule_id: 'rule_testing', enabled: true })); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/links.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/links.cy.ts index bd21fb72673d63..96bcfb7af864ae 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/links.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/links.cy.ts @@ -15,9 +15,10 @@ import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; describe('Rules talbes links', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { + login(); deleteAlertsAndRules(); createRule(getNewRule({ rule_id: 'rule1' })); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/machine_learning_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/machine_learning_rule.cy.ts index cceeb2b4962c4b..6c5de4f505af81 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/machine_learning_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/machine_learning_rule.cy.ts @@ -63,6 +63,9 @@ describe('Detection rules, machine learning', () => { before(() => { cleanKibana(); + }); + + beforeEach(() => { login(); visitWithoutDateRange(RULE_CREATION); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts index d455164d0931e0..7e46d5d67f6db4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts @@ -75,6 +75,7 @@ describe('New Terms rules', () => { beforeEach(() => { deleteAlertsAndRules(); + login(); }); it('Creates and enables a new terms rule', function () { diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts index 9d67eb3ed680a6..d4abbc3e5ac711 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts @@ -72,6 +72,9 @@ describe('Detection rules, override', () => { before(() => { cleanKibana(); + }); + + beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts index 71d2e6a77749b9..d063ca348c8d1e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/persistent_rules_table_state.cy.ts @@ -102,10 +102,10 @@ describe('Persistent rules table state', () => { before(() => { cleanKibana(); createTestRules(); - login(); }); beforeEach(() => { + login(); resetRulesTableState(); }); @@ -203,6 +203,7 @@ describe('Persistent rules table state', () => { describe('and on the rules management tab', () => { beforeEach(() => { + login(); visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); }); @@ -256,6 +257,7 @@ describe('Persistent rules table state', () => { describe('and on the rules monitoring tab', () => { beforeEach(() => { + login(); visit(SECURITY_DETECTIONS_RULES_MONITORING_URL); }); @@ -270,6 +272,10 @@ describe('Persistent rules table state', () => { }); describe('upon state format upgrade', async () => { + beforeEach(() => { + login(); + }); + describe('and having state in the url', () => { it('ignores unsupported state key', () => { visitRulesTableWithState({ @@ -313,6 +319,7 @@ describe('Persistent rules table state', () => { describe('when persisted state is partially unavailable', () => { describe('and on the rules management tab', () => { beforeEach(() => { + login(); visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); }); @@ -346,6 +353,7 @@ describe('Persistent rules table state', () => { describe('when corrupted', () => { describe('and on the rules management tab', () => { beforeEach(() => { + login(); visit(SECURITY_DETECTIONS_RULES_MANAGEMENT_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts index 66d6de71c34690..532bf5bf0bf244 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts @@ -37,10 +37,10 @@ import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; describe('Prebuilt rules', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); deleteAlertsAndRules(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); loadPrebuiltDetectionRules(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts index 5b316b4136708f..8f787a3826396e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/related_integrations.cy.ts @@ -54,8 +54,8 @@ Note that the rule we are using for testing purposes has the following character describe('Related integrations', () => { before(() => { - login(); cleanKibana(); + login(); importRule('related_integrations.ndjson'); }); @@ -71,6 +71,7 @@ describe('Related integrations', () => { }); beforeEach(() => { + login(); visit(DETECTIONS_RULE_MANAGEMENT_URL); waitForRulesTableToShow(); }); @@ -132,6 +133,7 @@ describe('Related integrations', () => { }); beforeEach(() => { + login(); visit(DETECTIONS_RULE_MANAGEMENT_URL); waitForRulesTableToShow(); }); @@ -216,6 +218,7 @@ describe('Related integrations', () => { }); beforeEach(() => { + login(); visit(DETECTIONS_RULE_MANAGEMENT_URL); waitForRulesTableToShow(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts index ab458e12dca2d5..04ff3adb786559 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_actions.cy.ts @@ -32,10 +32,10 @@ describe('Rule actions during detection rule creation', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); deleteAlertsAndRules(); deleteConnectors(); deleteIndex(indexConnector.index); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts index 9359e93f7fe478..37ac8ddadfe7af 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_selection.cy.ts @@ -51,10 +51,8 @@ describe.skip('Rules selection', () => { cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', availablePrebuiltRulesCount); }); - const bulkSelectButton = cy.get(SELECT_ALL_RULES_BTN); - // Un-select all rules via the Bulk Selection button from the Utility bar - bulkSelectButton.click(); + cy.get(SELECT_ALL_RULES_BTN).click(); // Current selection should be 0 rules cy.get(SELECTED_RULES_NUMBER_LABEL).should('contain.text', '0'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts index 38728635b63cf9..ec553a15c2b2f7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rules_table_auto_refresh.cy.ts @@ -43,6 +43,10 @@ describe.skip('Alerts detection rules table auto-refresh', () => { } }); + beforeEach(() => { + login(); + }); + it('Auto refreshes rules', () => { visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/sorting.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/sorting.cy.ts index 9dbf6a968746c1..36cd0161812530 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/sorting.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/sorting.cy.ts @@ -43,6 +43,10 @@ describe('Alerts detection rules', () => { createRule(getNewThresholdRule({ rule_id: '4' })); }); + beforeEach(() => { + login(); + }); + it('Sorts by enabled rules', () => { visit(DETECTIONS_RULE_MANAGEMENT_URL); waitForRulesTableToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts index f5ee340633e717..e7f96ded0b06e0 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts @@ -69,11 +69,11 @@ describe('Detection rules, threshold', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { deleteAlertsAndRules(); + login(); visitWithoutDateRange(RULE_CREATION); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/add_edit_flyout/flyout_validation.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/add_edit_flyout/flyout_validation.cy.ts index 21bdb963d1c59d..570f96f9357211 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/add_edit_flyout/flyout_validation.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/add_edit_flyout/flyout_validation.cy.ts @@ -67,7 +67,7 @@ import { getExceptionList } from '../../../objects/exception'; // to test in enzyme and very small changes can inadvertently add // bugs. As the complexity within the builder grows, these should // ensure the most basic logic holds. -describe('Exceptions flyout', { testIsolation: false }, () => { +describe('Exceptions flyout', () => { before(() => { esArchiverResetKibana(); // this is a made-up index that has just the necessary @@ -76,7 +76,6 @@ describe('Exceptions flyout', { testIsolation: false }, () => { esArchiverLoad('exceptions'); esArchiverLoad('conflicts_1'); esArchiverLoad('conflicts_2'); - login(); createExceptionList(getExceptionList(), getExceptionList().list_id).then((response) => createRule( getNewRule({ @@ -93,19 +92,18 @@ describe('Exceptions flyout', { testIsolation: false }, () => { }) ) ); + login(); + visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); }); beforeEach(() => { + login(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); goToRuleDetails(); cy.get(RULE_STATUS).should('have.text', '—'); goToExceptionsTab(); }); - afterEach(() => { - closeExceptionBuilderFlyout(); - }); - after(() => { esArchiverUnload('exceptions'); }); @@ -243,7 +241,8 @@ describe('Exceptions flyout', { testIsolation: false }, () => { addExceptionEntryFieldValueOfItemX('name{enter}', 1, 3); // This button will now read `Add non-nested button` cy.get(ADD_NESTED_BTN).scrollIntoView(); - cy.get(ADD_NESTED_BTN).focus().click(); + cy.get(ADD_NESTED_BTN).focus(); + cy.get(ADD_NESTED_BTN).click(); addExceptionEntryFieldValueOfItemX('@timestamp', 1, 4); // should have only deleted `user.id` diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts index 572e535d740fd7..da9db58fdb68d3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts @@ -44,7 +44,6 @@ const getExceptionList2 = () => ({ describe('Exceptions Table', () => { before(() => { esArchiverResetKibana(); - login(); // Create exception list associated with a rule createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) => @@ -67,6 +66,7 @@ describe('Exceptions Table', () => { 'exceptionListResponse' ); + login(); visitWithoutDateRange(EXCEPTIONS_URL); // Using cy.contains because we do not care about the exact text, @@ -74,6 +74,11 @@ describe('Exceptions Table', () => { cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3'); }); + beforeEach(() => { + login(); + visitWithoutDateRange(EXCEPTIONS_URL); + }); + it('Exports exception list', function () { cy.intercept(/(\/api\/exception_lists\/_export)/).as('export'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/list_management_flow/list_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/list_management_flow/list_details.cy.ts index 2321d101171f62..3221dea21adee6 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/list_management_flow/list_details.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/list_management_flow/list_details.cy.ts @@ -57,6 +57,7 @@ describe('Exception list management page', () => { }); beforeEach(() => { + login(); visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id)); waitForExceptionListDetailToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts index d9e11e61113ac5..4e9f58ca9330d3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_endpoint_exception.cy.ts @@ -58,7 +58,19 @@ describe('Add endpoint exception from rule details', () => { login(); deleteAlertsAndRules(); // create rule with exception - createEndpointExceptionList().then((response) => { + createEndpointExceptionList<{ + id: string; + list_id: string; + type: + | 'detection' + | 'rule_default' + | 'endpoint' + | 'endpoint_trusted_apps' + | 'endpoint_events' + | 'endpoint_host_isolation_exceptions' + | 'endpoint_blocklists'; + namespace_type: 'agnostic' | 'single'; + }>().then((response) => { createRule( getNewRule({ query: 'event.code:*', @@ -78,6 +90,7 @@ describe('Add endpoint exception from rule details', () => { }); beforeEach(() => { + login(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); goToRuleDetails(); goToEndpointExceptionsTab(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts index 7d35c01b578cd4..1b724f888b0d05 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception.cy.ts @@ -120,6 +120,7 @@ describe('Add/edit exception from rule details', () => { }); }); + login(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); goToRuleDetails(); goToExceptionsTab(); @@ -258,6 +259,7 @@ describe('Add/edit exception from rule details', () => { rule_id: 'rule_testing', }) ); + login(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); goToRuleDetails(); goToExceptionsTab(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts index 9ae70924e12b3a..7a442e211675d3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/add_edit_exception_data_view.cy.ts @@ -79,6 +79,7 @@ describe('Add exception using data views from rule details', () => { rule_id: 'rule_testing', }) ); + login(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); goToRuleDetails(); waitForAlertsToPopulate(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts index 43c3d0215e2fa5..54d999b41fbe9f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts @@ -51,11 +51,10 @@ describe('Exceptions viewer read only', () => { }) ); }); - - login(ROLES.reader); }); beforeEach(() => { + login(ROLES.reader); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL, ROLES.reader); goToRuleDetails(); goToExceptionsTab(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/add_edit_exception.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/add_edit_exception.cy.ts index 4ba1f4b8e7d448..8eeaa327e39e8d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/add_edit_exception.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/add_edit_exception.cy.ts @@ -26,9 +26,12 @@ describe('Add/edit exception from exception management page', () => { before(() => { esArchiverResetKibana(); esArchiverLoad('exceptions'); + createRule(getNewRule()); + }); + + beforeEach(() => { login(); visitWithoutDateRange(EXCEPTIONS_URL); - createRule(getNewRule()); }); after(() => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/exceptions_table.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/exceptions_table.cy.ts index c2f87af1e7e6be..11cfebc781fe93 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/exceptions_table.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/exceptions_table.cy.ts @@ -62,6 +62,7 @@ describe('Exceptions Table', () => { }); beforeEach(() => { + login(); visitWithoutDateRange(EXCEPTIONS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts index bc3e732538ff06..4fb7c6d011613a 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/exceptions/shared_exception_lists_management/manage_shared_exception_list.cy.ts @@ -85,6 +85,7 @@ describe('Manage shared exception list', () => { }); beforeEach(() => { + login(); visitWithoutDateRange(EXCEPTIONS_URL); waitForExceptionsTableToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/filters/pinned_filters.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/filters/pinned_filters.cy.ts index b152dbd7157bd5..72e6cee9e4042b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/filters/pinned_filters.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/filters/pinned_filters.cy.ts @@ -21,10 +21,13 @@ import { postDataView } from '../../tasks/common'; describe('pinned filters', () => { before(() => { - login(); postDataView('audit*'); }); + beforeEach(() => { + login(); + }); + it('show pinned filters on security', () => { visitWithoutDateRange(DISCOVER_WITH_PINNED_FILTER_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts index 5f025b1633c01d..cc6de32f148311 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts @@ -35,6 +35,7 @@ describe('Guided onboarding tour', () => { createRule(getNewRule({ query: 'user.name:*' })); }); beforeEach(() => { + login(); startAlertsCasesTour(); visit(ALERTS_URL); waitForAlertsToPopulate(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/header/navigation.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/header/navigation.cy.ts index ee031142894aa0..e38f91cb077307 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/header/navigation.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/header/navigation.cy.ts @@ -78,11 +78,8 @@ import { } from '../../screens/kibana_navigation'; describe('top-level navigation common to all pages in the Security app', () => { - before(() => { - login(); - }); - beforeEach(() => { + login(); visit(TIMELINES_URL); }); @@ -204,6 +201,7 @@ describe('top-level navigation common to all pages in the Security app', () => { describe('Kibana navigation to all pages in the Security app ', () => { beforeEach(() => { + login(); visit(KIBANA_HOME); openKibanaNavigation(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/header/search_bar.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/header/search_bar.cy.ts index 073ee64a8212f8..a138849a8a934c 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/header/search_bar.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/header/search_bar.cy.ts @@ -23,11 +23,8 @@ import { HOSTS_URL } from '../../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts'; describe('SearchBar', () => { - before(() => { - login(); - }); - beforeEach(() => { + login(); visit(HOSTS_URL); waitForAllHostsToBeLoaded(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/host_details/risk_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/host_details/risk_tab.cy.ts index 48a67ada67fbfe..89cb8c271dd6f9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/host_details/risk_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/host_details/risk_tab.cy.ts @@ -15,6 +15,9 @@ describe('risk tab', () => { before(() => { cleanKibana(); esArchiverLoad('risk_hosts'); + }); + + beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/hosts/events_viewer.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/events_viewer.cy.ts index c3600e459d4bd8..6a9ddc2be3352e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/hosts/events_viewer.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/hosts/events_viewer.cy.ts @@ -7,7 +7,6 @@ import { FIELDS_BROWSER_CHECKBOX, - FIELDS_BROWSER_CONTAINER, FIELDS_BROWSER_SELECTED_CATEGORIES_BADGES, FIELDS_BROWSER_VIEW_BUTTON, } from '../../screens/fields_browser'; @@ -31,7 +30,7 @@ import { openEventsViewerFieldsBrowser, waitsForEventsToBeLoaded, } from '../../tasks/hosts/events'; -import { clearSearchBar, kqlSearch } from '../../tasks/security_header'; +import { kqlSearch } from '../../tasks/security_header'; import { HOSTS_URL } from '../../urls/navigation'; import { resetFields } from '../../tasks/timeline'; @@ -50,7 +49,6 @@ const defaultHeadersInDefaultEcsCategory = [ describe('Events Viewer', () => { before(() => { esArchiverLoad('auditbeat_big'); - login(); }); after(() => { @@ -59,16 +57,12 @@ describe('Events Viewer', () => { context('Fields rendering', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openEvents(); openEventsViewerFieldsBrowser(); }); - afterEach(() => { - closeFieldsBrowser(); - cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); - }); - it('displays "view all" option by default', () => { cy.get(FIELDS_BROWSER_VIEW_BUTTON).should('contain.text', 'View: all'); }); @@ -86,15 +80,9 @@ describe('Events Viewer', () => { }); }); - context('Events viewer query modal', () => { - beforeEach(() => { - visit(HOSTS_URL); - openEvents(); - }); - }); - context('Events viewer fields behaviour', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openEvents(); openEventsViewerFieldsBrowser(); @@ -122,15 +110,12 @@ describe('Events Viewer', () => { context('Events behavior', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); }); - afterEach(() => { - clearSearchBar(); - }); - it('filters the events by applying filter criteria from the search bar at the top of the page', () => { const filterInput = 'aa7ca589f1b8220002f2fc61c64cfbf1'; // this will never match real data cy.get(SERVER_SIDE_EVENT_COUNT) diff --git a/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts index a5ff55899e669b..797cdcb339b0ba 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts @@ -26,10 +26,10 @@ describe('risk tab', () => { before(() => { cleanKibana(); esArchiverLoad('risk_hosts'); - login(); }); beforeEach(() => { + login(); visit(HOSTS_URL); navigateToHostRiskDetailTab(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/hosts/hosts_risk_column.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/hosts_risk_column.cy.ts index ef25579e655f8d..af00b2786adfa9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/hosts/hosts_risk_column.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/hosts/hosts_risk_column.cy.ts @@ -17,6 +17,9 @@ describe('All hosts table', () => { before(() => { cleanKibana(); esArchiverLoad('risk_hosts'); + }); + + beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/inspect/inspect_button.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/inspect/inspect_button.cy.ts index 19073735c38833..d8e9b0c5a46381 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/inspect/inspect_button.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/inspect/inspect_button.cy.ts @@ -46,6 +46,7 @@ describe('Inspect Explore pages', () => { * Group all tests of a page into one "it" call to improve speed */ it(`inspect ${pageName} page`, () => { + login(); visit(url); waitForPageToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alert_table_action_column.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alert_table_action_column.cy.ts index dceaf36364c963..723cbeca112fe3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alert_table_action_column.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alert_table_action_column.cy.ts @@ -7,8 +7,6 @@ import { OVERLAY_CONTAINER } from '../../../screens/alerts'; import { - closeAnalyzer, - closeSessionViewerFromAlertTable, openAnalyzerForFirstAlertInTimeline, openSessionViewerFromAlertTable, } from '../../../tasks/alerts'; @@ -18,10 +16,13 @@ import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver'; import { login, visit } from '../../../tasks/login'; import { ALERTS_URL } from '../../../urls/navigation'; -describe('Alerts Table Action column', { testIsolation: false }, () => { +describe('Alerts Table Action column', () => { before(() => { cleanKibana(); esArchiverLoad('process_ancestry'); + }); + + beforeEach(() => { login(); visit(ALERTS_URL); waitForAlertsToPopulate(); @@ -34,14 +35,10 @@ describe('Alerts Table Action column', { testIsolation: false }, () => { it('should have session viewer button visible & open session viewer on click', () => { openSessionViewerFromAlertTable(); cy.get(OVERLAY_CONTAINER).should('be.visible'); - // cleanup - closeSessionViewerFromAlertTable(); }); it('should have analyzer button visible & open analyzer on click', () => { openAnalyzerForFirstAlertInTimeline(); cy.get(OVERLAY_CONTAINER).should('be.visible'); - // cleanup - closeAnalyzer(); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts index 19714548df1908..7b80498aad9d72 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_cell_actions.cy.ts @@ -8,7 +8,6 @@ import { getNewRule } from '../../../objects/rule'; import { CELL_COPY_BUTTON, FILTER_BADGE, SHOW_TOP_N_HEADER } from '../../../screens/alerts'; import { - ALERT_TABLE_ACTIONS_HEADER, ALERT_TABLE_FILE_NAME_HEADER, ALERT_TABLE_FILE_NAME_VALUES, ALERT_TABLE_SEVERITY_HEADER, @@ -23,7 +22,6 @@ import { showTopNAlertProperty, clickExpandActions, filterOutAlertProperty, - closeTopNAlertProperty, } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; import { cleanKibana } from '../../../tasks/common'; @@ -34,24 +32,22 @@ import { fillAddFilterForm, fillKqlQueryBar, openAddFilterPopover, - removeKqlFilter, } from '../../../tasks/search_bar'; -import { closeTimeline, openActiveTimeline, removeDataProvider } from '../../../tasks/timeline'; +import { openActiveTimeline } from '../../../tasks/timeline'; import { ALERTS_URL } from '../../../urls/navigation'; -describe('Alerts cell actions', { testIsolation: false }, () => { + +describe('Alerts cell actions', () => { before(() => { cleanKibana(); - login(); createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); }); describe('Filter', () => { - afterEach(() => { - removeKqlFilter(); - scrollAlertTableColumnIntoView(ALERT_TABLE_ACTIONS_HEADER); + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); }); it('should filter for a non-empty property', () => { @@ -104,10 +100,10 @@ describe('Alerts cell actions', { testIsolation: false }, () => { }); describe('Add to timeline', () => { - afterEach(() => { - removeDataProvider(); - closeTimeline(); - scrollAlertTableColumnIntoView(ALERT_TABLE_ACTIONS_HEADER); + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); }); it('should add a non-empty property to default timeline', () => { @@ -137,9 +133,10 @@ describe('Alerts cell actions', { testIsolation: false }, () => { }); describe('Show Top N', () => { - afterEach(() => { - closeTopNAlertProperty(); - scrollAlertTableColumnIntoView(ALERT_TABLE_ACTIONS_HEADER); + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); }); it('should show top for a property', () => { @@ -155,8 +152,10 @@ describe('Alerts cell actions', { testIsolation: false }, () => { }); describe('Copy to clipboard', () => { - afterEach(() => { - scrollAlertTableColumnIntoView(ALERT_TABLE_ACTIONS_HEADER); + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); }); it('should copy to clipboard', () => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts index f389d52473eec6..93b49c825fa4a7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -56,12 +56,15 @@ describe('Alert details flyout', () => { }); }); - describe('With unmapped fields', { testIsolation: false }, () => { + describe('With unmapped fields', () => { before(() => { cleanKibana(); esArchiverLoad('unmapped_fields'); - login(); createRule(getUnmappedRule()); + }); + + beforeEach(() => { + login(); visitWithoutDateRange(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); @@ -118,16 +121,16 @@ describe('Alert details flyout', () => { }); }); - describe('Url state management', { testIsolation: false }, () => { + describe('Url state management', () => { before(() => { cleanKibana(); esArchiverLoad('query_alert'); - login(); - visit(ALERTS_URL); - waitForAlertsToPopulate(); }); beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); expandFirstAlert(); }); @@ -163,16 +166,16 @@ describe('Alert details flyout', () => { }); }); - describe('Localstorage management', { testIsolation: false }, () => { + describe('Localstorage management', () => { before(() => { cleanKibana(); esArchiverLoad('query_alert'); - login(); - visit(ALERTS_URL); - waitForAlertsToPopulate(); }); beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); expandFirstAlert(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts index f4c993b0d421af..e64995050b968d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts @@ -43,13 +43,15 @@ describe('Changing alert status', () => { before(() => { esArchiverLoad('auditbeat_big'); cleanKibana(); - login(); }); + after(() => { esArchiverUnload('auditbeat_big'); }); + context('Opening alerts', () => { beforeEach(() => { + login(); createRule(getNewRule()); visit(ALERTS_URL); waitForAlertsToPopulate(); @@ -156,6 +158,7 @@ describe('Changing alert status', () => { }); context('Closing alerts', () => { beforeEach(() => { + login(); deleteAlertsAndRules(); createRule(getNewRule({ rule_id: '1', max_signals: 100 })); visit(ALERTS_URL); @@ -305,17 +308,17 @@ describe('Changing alert status', () => { }); }); - context('Changing alert status with read only role', () => { - before(() => { - login(ROLES.t2_analyst); - }); + // TODO: Are you sure that read only role should be able to close alerts? + context.skip('Changing alert status with read only role', () => { beforeEach(() => { + login(ROLES.t2_analyst); deleteAlertsAndRules(); createRule(getNewRule()); visit(ALERTS_URL); waitForAlertsToPopulate(); selectCountTable(); }); + it.skip('Mark one alert as acknowledged when more than one open alerts are selected', () => { cy.get(ALERTS_COUNT) .invoke('text') diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts index 86d5872c2b37e8..3c231fb6576303 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts @@ -12,7 +12,6 @@ import { CONTROL_FRAME_TITLE, FILTER_GROUP_CHANGED_BANNER, FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS, - FILTER_GROUP_SAVE_CHANGES_POPOVER, OPTION_LIST_LABELS, OPTION_LIST_VALUES, OPTION_SELECTABLE, @@ -101,17 +100,16 @@ const assertFilterControlsWithFilterObject = (filterObject = DEFAULT_DETECTION_P }); }; -describe('Detections : Page Filters', { testIsolation: false }, () => { +describe('Detections : Page Filters', () => { before(() => { cleanKibana(); - login(); createRule(getNewRule({ rule_id: 'custom_rule_filters' })); - visit(ALERTS_URL); - waitForAlerts(); - waitForPageFilters(); }); - afterEach(() => { + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlerts(); resetFilters(); }); @@ -119,10 +117,14 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { assertFilterControlsWithFilterObject(); }); - context('Alert Page Filters Customization ', { testIsolation: false }, () => { + context('Alert Page Filters Customization ', () => { beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlerts(); resetFilters(); }); + it('should be able to delete Controls', () => { waitForPageFilters(); editFilterGroupControls(); @@ -132,6 +134,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { }); discardFilterGroupControls(); }); + it('should be able to add new Controls', () => { const fieldName = 'event.module'; const label = 'EventModule'; @@ -142,20 +145,20 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { label, }); cy.get(CONTROL_FRAME_TITLE).should('contain.text', label); - cy.get(FILTER_GROUP_SAVE_CHANGES_POPOVER).should('be.visible'); discardFilterGroupControls(); cy.get(CONTROL_FRAME_TITLE).should('not.contain.text', label); }); + it('should be able to edit Controls', () => { const fieldName = 'event.module'; const label = 'EventModule'; editFilterGroupControls(); editFilterGroupControl({ idx: 3, fieldName, label }); cy.get(CONTROL_FRAME_TITLE).should('contain.text', label); - cy.get(FILTER_GROUP_SAVE_CHANGES_POPOVER).should('be.visible'); discardFilterGroupControls(); cy.get(CONTROL_FRAME_TITLE).should('not.contain.text', label); }); + it('should not sync to the URL in edit mode but only in view mode', () => { cy.url().then((urlString) => { editFilterGroupControls(); @@ -182,7 +185,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { const currURL = new URL(url); currURL.searchParams.set('pageFilters', encode(formatPageFilterSearchParam(NEW_FILTERS))); - cy.visit(currURL.toString()); + visit(currURL.toString()); waitForAlerts(); assertFilterControlsWithFilterObject(NEW_FILTERS); }); @@ -203,7 +206,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { const currURL = new URL(url); currURL.searchParams.set('pageFilters', encode(pageFilterUrlString)); - cy.visit(currURL.toString()); + visit(currURL.toString()); waitForAlerts(); cy.get(OPTION_LIST_LABELS).should((sub) => { @@ -222,7 +225,6 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { it(`Alert list is updated when the alerts are updated`, () => { // mark status of one alert to be acknowledged - cy.visit(ALERTS_URL); selectCountTable(); cy.get(ALERTS_COUNT) .invoke('text') @@ -241,8 +243,6 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { }); it(`URL is updated when filters are updated`, () => { - cy.visit(ALERTS_URL); - cy.on('url:changed', (urlString) => { const NEW_FILTERS = DEFAULT_DETECTION_PAGE_FILTERS.map((filter) => { if (filter.title === 'Severity') { @@ -261,8 +261,6 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { }); it(`Filters are restored from localstorage when user navigates back to the page.`, () => { - // change severity filter to high - cy.visit(ALERTS_URL); cy.get(OPTION_LIST_VALUES(1)).click(); cy.get(OPTION_SELECTABLE(1, 'high')).should('be.visible'); cy.get(OPTION_SELECTABLE(1, 'high')).click({ force: true }); @@ -297,6 +295,7 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { saveFilterGroupControls(); cy.get(FILTER_GROUP_CHANGED_BANNER).should('not.exist'); }); + it('Changed banner should hide on discarding changes', () => { visitAlertsPageWithCustomFilters(customFilters); waitForPageFilters(); @@ -316,9 +315,9 @@ describe('Detections : Page Filters', { testIsolation: false }, () => { const idx = 3; const { FILTER_FIELD_TYPE, FIELD_TYPES } = FILTER_GROUP_EDIT_CONTROL_PANEL_ITEMS; editFilterGroupControls(); - cy.get(CONTROL_FRAME_TITLE).eq(idx).trigger('mouseover'); - cy.get(FILTER_GROUP_CONTROL_ACTION_EDIT(idx)).trigger('click', { force: true }); - cy.get(FILTER_FIELD_TYPE).should('be.visible').trigger('click'); + cy.get(CONTROL_FRAME_TITLE).eq(idx).realHover(); + cy.get(FILTER_GROUP_CONTROL_ACTION_EDIT(idx)).click(); + cy.get(FILTER_FIELD_TYPE).click(); cy.get(FIELD_TYPES.STRING).should('be.visible'); cy.get(FIELD_TYPES.BOOLEAN).should('be.visible'); cy.get(FIELD_TYPES.IP).should('be.visible'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts index 2ce3977eeca709..ed6c940de2370d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel.cy.ts @@ -48,108 +48,115 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -// Skipping these for now as the feature is protected behind a feature flag set to false by default -// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip('Alert details expandable flyout left panel', { testIsolation: false }, () => { - before(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - }); - - it('should display 4 tabs in the header', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) - .should('be.visible') - .and('have.text', 'Visualize'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) - .should('be.visible') - .and('have.text', 'Investigation'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB).should('be.visible').and('have.text', 'History'); - }); - - it('should display tab content when switching tabs', () => { - openVisualizeTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); - - openInsightsTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - - openInvestigationTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - - openHistoryTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB_CONTENT).should('be.visible'); - }); - - describe('visualize tab', () => { - it('should display a button group with 2 button in the visualize tab', () => { - openVisualizeTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) - .should('be.visible') - .and('have.text', 'Session View'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) - .should('be.visible') - .and('have.text', 'Analyzer Graph'); +describe( + 'Alert details expandable flyout left panel', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + before(() => { + cleanKibana(); + createRule(getNewRule()); }); - it('should display content when switching buttons', () => { - openVisualizeTab(); - openSessionView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT).should('be.visible'); - - openGraphAnalyzer(); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); }); - }); - describe('insights tab', () => { - it('should display a button group with 4 button in the insights tab', () => { - openInsightsTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) - .should('be.visible') - .and('have.text', 'Entities'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) + it('should display 4 tabs in the header', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) .should('be.visible') - .and('have.text', 'Threat Intelligence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) + .and('have.text', 'Visualize'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) .should('be.visible') - .and('have.text', 'Prevalence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) + .and('have.text', 'Insights'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) .should('be.visible') - .and('have.text', 'Correlations'); + .and('have.text', 'Investigation'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB).should('be.visible').and('have.text', 'History'); }); - it('should display content when switching buttons', () => { + it.skip('should display tab content when switching tabs', () => { + openVisualizeTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + openInsightsTab(); - openEntities(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - openThreatIntelligence(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_CONTENT) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); + openInvestigationTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - openPrevalence(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_CONTENT) - .should('be.visible') - .and('have.text', 'Prevalence'); + openHistoryTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_HISTORY_TAB_CONTENT).should('be.visible'); + }); - openCorrelations(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_CONTENT) - .should('be.visible') - .and('have.text', 'Correlations'); + describe.skip('visualize tab', () => { + it('should display a button group with 2 button in the visualize tab', () => { + openVisualizeTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) + .should('be.visible') + .and('have.text', 'Session View'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) + .should('be.visible') + .and('have.text', 'Analyzer Graph'); + }); + + it('should display content when switching buttons', () => { + openVisualizeTab(); + openSessionView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT).should('be.visible'); + + openGraphAnalyzer(); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); + }); }); - }); - describe('investigation tab', () => { - it('should display investigation guide', () => { - openInvestigationTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); + describe.skip('insights tab', () => { + it('should display a button group with 4 button in the insights tab', () => { + openInsightsTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) + .should('be.visible') + .and('have.text', 'Entities'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Prevalence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) + .should('be.visible') + .and('have.text', 'Correlations'); + }); + + it('should display content when switching buttons', () => { + openInsightsTab(); + openEntities(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + + openThreatIntelligence(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_CONTENT) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); + + openPrevalence(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_CONTENT) + .should('be.visible') + .and('have.text', 'Prevalence'); + + openCorrelations(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_CONTENT) + .should('be.visible') + .and('have.text', 'Correlations'); + }); + }); + + describe.skip('investigation tab', () => { + it('should display investigation guide', () => { + openInvestigationTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); + }); }); - }); -}); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts index dbf9782ccc541c..a38f5a374a1641 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts @@ -23,7 +23,7 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 describe.skip( 'Alert details expandable flyout left panel analyzer graph', - { testIsolation: false }, + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { before(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts index e5935613062514..71983473b7ac16 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts @@ -26,7 +26,7 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 describe.skip( 'Alert details expandable flyout left panel entities', - { testIsolation: false }, + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { before(() => { cleanKibana(); @@ -41,9 +41,11 @@ describe.skip( }); it('should display analyzer graph and node list', () => { + // eslint-disable-next-line cypress/unsafe-to-chain-command cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS) .scrollIntoView() .should('be.visible'); + // eslint-disable-next-line cypress/unsafe-to-chain-command cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS) .scrollIntoView() .should('be.visible'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts index f5b56f84182497..31c410dff502e2 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts @@ -21,7 +21,7 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 describe.skip( 'Alert details expandable flyout left panel session view', - { testIsolation: false }, + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { before(() => { cleanKibana(); @@ -40,6 +40,6 @@ describe.skip( .and('contain.text', 'No process events found for this query'); }); - it('should display session view component', () => {}); + // it('should display session view component', () => {}); } ); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts index ac3c6d58d99fe9..c1cddd0d61e61f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts @@ -13,16 +13,20 @@ import { ALERTS_URL } from '../../../../urls/navigation'; // Skipping these for now as the feature is protected behind a feature flag set to false by default // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip('Expandable flyout left panel threat intelligence', { testIsolation: false }, () => { - before(() => { - cleanKibana(); - login(); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); +describe.skip( + 'Expandable flyout left panel threat intelligence', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + before(() => { + cleanKibana(); + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + }); - it('should serialize its state to url', () => { - cy.url().should('include', 'eventFlyout'); - }); -}); + it('should serialize its state to url', () => { + cy.url().should('include', 'eventFlyout'); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index 1e045995a3c39f..cd02b424e46976 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -34,57 +34,63 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // Skipping these for now as the feature is protected behind a feature flag set to false by default // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip('Alert details expandable flyout right panel', { testIsolation: false }, () => { - const rule = getNewRule(); +describe.skip( + 'Alert details expandable flyout right panel', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + const rule = getNewRule(); - before(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); + before(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + }); - it('should display title in the header', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - }); + it('should display title in the header', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + }); - it('should toggle expand detail button in the header', () => { - expandDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Collapse alert details'); + it('should toggle expand detail button in the header', () => { + expandDocumentDetailsExpandableFlyoutLeftSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) + .should('be.visible') + .and('have.text', 'Collapse alert details'); - collapseDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Expand alert details'); - }); + collapseDocumentDetailsExpandableFlyoutLeftSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) + .should('be.visible') + .and('have.text', 'Expand alert details'); + }); - it('should display 3 tabs in the right section', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).should('be.visible').and('have.text', 'Overview'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); - }); + it('should display 3 tabs in the right section', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB) + .should('be.visible') + .and('have.text', 'Overview'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); + }); - it('should display tab content when switching tabs in the right section', () => { - openOverviewTab(); - // we shouldn't need to test anything here as it's covered with the new overview_tab file + it('should display tab content when switching tabs in the right section', () => { + openOverviewTab(); + // we shouldn't need to test anything here as it's covered with the new overview_tab file - openTableTab(); - // the table component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that scrolls to a specific element in the table to ensure Cypress finds it - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); + openTableTab(); + // the table component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that scrolls to a specific element in the table to ensure Cypress finds it + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_EVENT_TYPE_ROW).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible'); - // scroll back up to the top to open the json tab - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).scrollIntoView(); + // scroll back up to the top to open the json tab + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).scrollIntoView(); - openJsonTab(); - // the json component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that vertically scrolls down to ensure Cypress finds it - scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 6500); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); - }); -}); + openJsonTab(); + // the json component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that vertically scrolls down to ensure Cypress finds it + scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 6500); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts index e27ed5b581799f..8cf2b0c2a532ed 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_footer.cy.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable cypress/unsafe-to-chain-command */ + import { CASE_ACTION_WRAPPER, CASE_ELLIPSE_BUTTON, @@ -70,7 +72,7 @@ const deleteCase = () => { // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 describe.skip( 'Alert details expandable flyout right panel footer', - { testIsolation: false }, + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { before(() => { cleanKibana(); @@ -171,7 +173,7 @@ describe.skip( // TODO figure out why isolate host isn't showing up in the dropdown // https://github.com/elastic/security-team/issues/6302 - it.skip('should isolate host', () => {}); + // it.skip('should isolate host', () => {}); // TODO this will change when respond is improved // https://github.com/elastic/security-team/issues/6303 diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts index 4c36ee6ab383c0..13dbf7c1634199 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_header.cy.ts @@ -25,7 +25,7 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 describe.skip( 'Alert details expandable flyout right panel header', - { testIsolation: false }, + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { const rule = getNewRule(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index ba36ed2f70cd8e..6e81a34de222a7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -5,6 +5,8 @@ * 2.0. */ +/* eslint-disable cypress/unsafe-to-chain-command */ + import { DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE, @@ -62,7 +64,7 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 describe.skip( 'Alert details expandable flyout right panel overview tab', - { testIsolation: false }, + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, () => { const rule = getNewRule(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts index 2457115e8ce262..2f50e234b9adda 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts @@ -33,46 +33,50 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; // Skipping these for now as the feature is protected behind a feature flag set to false by default // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip('Alert details expandable flyout right panel', { testIsolation: false }, () => { - before(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - openTableTab(); - }); +describe.skip( + 'Alert details expandable flyout right panel', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + before(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + openTableTab(); + }); - it('should display and filter the table', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW).should('be.visible'); - filterTableTabTable('timestamp'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); - clearFilterTableTabTable(); - }); + it('should display and filter the table', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW).should('be.visible'); + filterTableTabTable('timestamp'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); + clearFilterTableTabTable(); + }); - it('should test filter in cell actions', () => { - filterInTableTabTable(); - cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); - removeKqlFilter(); - }); + it('should test filter in cell actions', () => { + filterInTableTabTable(); + cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); + removeKqlFilter(); + }); - it('should test filter out cell actions', () => { - filterOutTableTabTable(); - cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); - removeKqlFilter(); - }); + it('should test filter out cell actions', () => { + filterOutTableTabTable(); + cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); + removeKqlFilter(); + }); - it('should test add to timeline cell actions', () => { - addToTimelineTableTabTable(); - openActiveTimeline(); - cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); - closeTimeline(); - }); + it('should test add to timeline cell actions', () => { + addToTimelineTableTabTable(); + openActiveTimeline(); + cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); + closeTimeline(); + }); - it('should test copy to clipboard cell actions', () => { - copyToClipboardTableTabTable(); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); - }); -}); + it('should test copy to clipboard cell actions', () => { + copyToClipboardTableTabTable(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts index 5167fdfaefa815..33683a86b8227e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts @@ -19,37 +19,41 @@ import { // Skipping these for now as the feature is protected behind a feature flag set to false by default // To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50 -describe.skip('Expandable flyout state sync', { testIsolation: false }, () => { - const rule = getNewRule(); - - before(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - }); - - it('should serialize its state to url', () => { - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - }); - - it('should reopen the flyout after browser refresh', () => { - cy.reload(); - - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - }); - - it('should clear the url state when flyout is closed', () => { - cy.reload(); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - - cy.get(DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON).click(); - - cy.url().should('not.include', 'eventFlyout'); - }); -}); +describe.skip( + 'Expandable flyout state sync', + { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, + () => { + const rule = getNewRule(); + + before(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + }); + + it('should serialize its state to url', () => { + cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + }); + + it('should reopen the flyout after browser refresh', () => { + cy.reload(); + + cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + }); + + it('should clear the url state when flyout is closed', () => { + cy.reload(); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + + cy.get(DOCUMENT_DETAILS_FLYOUT_CLOSE_BUTTON).click(); + + cy.url().should('not.include', 'eventFlyout'); + }); + } +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts index d654d83330df37..5a8c382bdf4afd 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { closeTimeline } from '../../../tasks/timeline'; import { getNewRule } from '../../../objects/rule'; import { PROVIDER_BADGE, QUERY_TAB_BUTTON, TIMELINE_TITLE } from '../../../screens/timeline'; import { FILTER_BADGE } from '../../../screens/alerts'; @@ -27,18 +26,17 @@ import { } from '../../../screens/alerts_details'; import { verifyInsightCount } from '../../../tasks/alerts_details'; -describe('Investigate in timeline', { testIsolation: false }, () => { +describe('Investigate in timeline', () => { before(() => { cleanKibana(); - login(); createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); }); describe('From alerts table', () => { - after(() => { - closeTimeline(); + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); }); it('should open new timeline from alerts table', () => { @@ -47,33 +45,30 @@ describe('Investigate in timeline', { testIsolation: false }, () => { .first() .invoke('text') .then((eventId) => { - investigateFirstAlertInTimeline(); cy.get(PROVIDER_BADGE).filter(':visible').should('have.text', eventId); }); }); }); describe('From alerts details flyout', () => { - before(() => { + beforeEach(() => { + login(); + visit(ALERTS_URL); + waitForAlertsToPopulate(); expandFirstAlert(); }); - afterEach(() => { - closeTimeline(); - }); - it('should open a new timeline from a prevalence field', () => { // Only one alert matches the exact process args in this case const alertCount = 1; // Click on the last button that lets us investigate in timeline. // We expect this to be the `process.args` row. - const investigateButton = cy - .get(ALERT_FLYOUT) + cy.get(ALERT_FLYOUT) .find(SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON) - .last(); - investigateButton.should('have.text', alertCount); - investigateButton.click(); + .last() + .should('have.text', alertCount) + .click(); // Make sure a new timeline is created and opened cy.get(TIMELINE_TITLE).should('have.text', 'Untitled timeline'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/resolver.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/resolver.cy.ts index c1c60034642d3e..8da8aa484607dc 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/resolver.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/resolver.cy.ts @@ -20,10 +20,11 @@ import { ALERTS_URL } from '../../../urls/navigation'; describe('Analyze events view for alerts', () => { before(() => { cleanKibana(); - login(); createRule(getNewRule()); }); + beforeEach(() => { + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); }); @@ -33,8 +34,7 @@ describe('Analyze events view for alerts', () => { cy.get(ANALYZER_NODE).first().should('be.visible'); }); - it(`should display - a toast indicating the date range of found events when a time range has 0 events in it`, () => { + it('should display a toast indicating the date range of found events when a time range has 0 events in it', () => { const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000'; setStartDate(dateContainingZeroEvents); waitForAlertsToPopulate(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts index 23d302afe5ad18..baf852e59c53f7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/dasbhoards/detection_response.cy.ts @@ -24,7 +24,6 @@ import { USER_TABLE_ROW_TOTAL_ALERTS, USER_TABLE_USER_NAME_BTN, } from '../../../screens/detection_response'; -import { DETECTION_RESPONSE } from '../../../screens/security_header'; import { QUERY_TAB_BUTTON, TIMELINE_DATA_PROVIDERS_CONTAINER } from '../../../screens/timeline'; import { waitForAlerts } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; @@ -32,8 +31,8 @@ import { cleanKibana } from '../../../tasks/common'; import { investigateDashboardItemInTimeline } from '../../../tasks/dashboards/common'; import { waitToNavigateAwayFrom } from '../../../tasks/kibana_navigation'; import { login, visit } from '../../../tasks/login'; -import { clearSearchBar, kqlSearch, navigateFromHeaderTo } from '../../../tasks/security_header'; -import { closeTimeline } from '../../../tasks/timeline'; +import { clearSearchBar, kqlSearch } from '../../../tasks/security_header'; +import { createNewTimeline } from '../../../tasks/timeline'; import { ALERTS_URL, DASHBOARDS_URL, DETECTIONS_RESPONSE_URL } from '../../../urls/navigation'; const TEST_USER_NAME = 'test'; @@ -42,12 +41,15 @@ const SIEM_KIBANA_HOST_NAME = 'siem-kibana'; describe('Detection response view', () => { before(() => { cleanKibana(); - login(); createRule(getNewRule()); + }); + + beforeEach(() => { + login(); visit(DETECTIONS_RESPONSE_URL); }); - context('KQL search bar', { testIsolation: false }, () => { + context('KQL search bar', () => { it(`filters out hosts with KQL search bar query`, () => { kqlSearch(`host.name : fakeHostName{enter}`); @@ -89,9 +91,9 @@ describe('Detection response view', () => { }); }); - context('Open in timeline', { testIsolation: false }, () => { + context('Open in timeline', () => { afterEach(() => { - closeTimeline(); + createNewTimeline(); }); it(`opens timeline with correct query count for hosts by alert severity table`, () => { @@ -157,11 +159,7 @@ describe('Detection response view', () => { }); }); - context('Redirection to AlertPage', { testIsolation: false }, () => { - afterEach(() => { - navigateFromHeaderTo(DETECTION_RESPONSE); - }); - + context('Redirection to AlertPage', () => { it('should redirect to alert page with host and status as the filters', () => { cy.get(HOST_TABLE_ROW_TOTAL_ALERTS) .first() @@ -173,7 +171,7 @@ describe('Detection response view', () => { .should('be.visible') .then((hostNameEl) => { const hostName = hostNameEl.text(); - sub.trigger('click'); + sub.click(); waitToNavigateAwayFrom(DASHBOARDS_URL); cy.url().should((urlString) => { const url = new URL(urlString); @@ -201,7 +199,7 @@ describe('Detection response view', () => { .first() .should('be.visible') .then((hostNameEl) => { - cy.get(HOST_TABLE_ROW_SEV(severityVal)).first().trigger('click'); + cy.get(HOST_TABLE_ROW_SEV(severityVal)).first().click(); waitToNavigateAwayFrom(DASHBOARDS_URL); const hostName = hostNameEl.text(); waitForAlerts(); @@ -227,7 +225,7 @@ describe('Detection response view', () => { .should('be.visible') .then((userNameEl) => { const userName = userNameEl.text(); - sub.trigger('click'); + sub.click(); waitToNavigateAwayFrom(DASHBOARDS_URL); cy.url().should((urlString) => { const url = new URL(urlString); @@ -256,7 +254,7 @@ describe('Detection response view', () => { .should('be.visible') .then((userNameEl) => { const userName = userNameEl.text(); - cy.get(USER_TABLE_ROW_SEV(severityVal)).trigger('click'); + cy.get(USER_TABLE_ROW_SEV(severityVal)).click(); waitToNavigateAwayFrom(DASHBOARDS_URL); waitForAlerts(); cy.get(ALERTS_COUNT).should('be.visible').should('have.text', `${alertCount} alerts`); @@ -280,7 +278,7 @@ describe('Detection response view', () => { .first() .should('be.visible') .then((ruleNameEl) => { - sub.trigger('click'); + sub.click(); waitToNavigateAwayFrom(DASHBOARDS_URL); const ruleName = ruleNameEl.text(); waitForAlerts(); @@ -294,17 +292,12 @@ describe('Detection response view', () => { }); }); it('should redirect to "View Open Alerts" correctly', () => { - cy.get(RULE_TABLE_VIEW_ALL_OPEN_ALERTS_BTN) - .first() - .should('be.visible') - .then((sub) => { - sub.trigger('click'); - waitToNavigateAwayFrom(DASHBOARDS_URL); - waitForAlerts(); - cy.get(CONTROL_FRAMES).should('have.length', 1); - cy.get(OPTION_LIST_LABELS).eq(0).should('have.text', `Status`); - cy.get(OPTION_LIST_VALUES(0)).should('have.text', 'open1'); - }); + cy.get(RULE_TABLE_VIEW_ALL_OPEN_ALERTS_BTN).first().click(); + waitToNavigateAwayFrom(DASHBOARDS_URL); + waitForAlerts(); + cy.get(CONTROL_FRAMES).should('have.length', 1); + cy.get(OPTION_LIST_LABELS).eq(0).should('have.text', `Status`); + cy.get(OPTION_LIST_VALUES(0)).should('have.text', 'open1'); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/creation.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/creation.cy.ts index dc86ba0989e278..c2cbf8ecf5d2d1 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/creation.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/creation.cy.ts @@ -51,14 +51,15 @@ import { TIMELINES_URL } from '../../../urls/navigation'; describe('Timeline Templates', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { + login(); deleteTimelines(); cy.intercept('PATCH', '/api/timeline').as('timeline'); }); - it('Creates a timeline template', async () => { + it.skip('Creates a timeline template', () => { visitWithoutDateRange(TIMELINES_URL); openTimelineUsingToggle(); createNewTimelineTemplate(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/export.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/export.cy.ts index ef5db31e4cc3aa..bdfbf897d01a18 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/export.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timeline_templates/export.cy.ts @@ -20,19 +20,20 @@ import { searchByTitle } from '../../../tasks/table_pagination'; describe('Export timelines', () => { before(() => { cleanKibana(); - cy.intercept({ - method: 'POST', - path: '/api/timeline/_export?file_name=timelines_export.ndjson', - }).as('export'); + createTimelineTemplate(getTimelineTemplate()).then((response) => { cy.wrap(response).as('templateResponse'); cy.wrap(response.body.data.persistTimeline.timeline.savedObjectId).as('templateId'); cy.wrap(response.body.data.persistTimeline.timeline.title).as('templateTitle'); }); - login(); }); it('Exports a custom timeline template', function () { + cy.intercept({ + method: 'POST', + path: '/api/timeline/_export?file_name=timelines_export.ndjson', + }).as('export'); + login(); visitWithoutDateRange(TIMELINE_TEMPLATES_URL); searchByTitle(this.templateTitle); exportTimeline(this.templateId); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/bulk_add_to_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/bulk_add_to_timeline.cy.ts index 2c74534c984dfd..e8dc6e48a48151 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/bulk_add_to_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/bulk_add_to_timeline.cy.ts @@ -40,6 +40,7 @@ describe('Bulk Investigate in Timeline', () => { }); beforeEach(() => { + login(); visit(ALERTS_URL); waitForAlertsToPopulate(); }); @@ -69,6 +70,7 @@ describe('Bulk Investigate in Timeline', () => { context('Host -> Events Viewer', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openEvents(); waitsForEventsToBeLoaded(); @@ -99,6 +101,7 @@ describe('Bulk Investigate in Timeline', () => { context('Host -> Sessions Viewer', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openSessions(); waitsForEventsToBeLoaded(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/creation.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/creation.cy.ts index 6c03d1f6999312..6c2e92cff63c58 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/creation.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/creation.cy.ts @@ -51,10 +51,12 @@ import { EQL_QUERY_VALIDATION_ERROR } from '../../../screens/create_new_rule'; describe('Create a timeline from a template', () => { before(() => { deleteTimelines(); + login(); createTimelineTemplate(getTimeline()); }); beforeEach(() => { + login(); visitWithoutDateRange(TIMELINE_TEMPLATES_URL); }); @@ -77,7 +79,7 @@ describe('Timelines', (): void => { describe('Toggle create timeline from plus icon', () => { context('Privileges: CRUD', () => { - before(() => { + beforeEach(() => { login(); visit(OVERVIEW_URL); }); @@ -90,7 +92,7 @@ describe('Timelines', (): void => { }); context('Privileges: READ', () => { - before(() => { + beforeEach(() => { login(ROLES.reader); visit(OVERVIEW_URL, undefined, ROLES.reader); }); @@ -99,7 +101,7 @@ describe('Timelines', (): void => { createNewTimeline(); cy.get(TIMELINE_PANEL).should('be.visible'); cy.get(EDIT_TIMELINE_BTN).should('be.disabled'); - cy.get(EDIT_TIMELINE_BTN).first().trigger('mouseover', { force: true }); + cy.get(EDIT_TIMELINE_BTN).first().realHover(); cy.get(EDIT_TIMELINE_TOOLTIP).should('be.visible'); cy.get(EDIT_TIMELINE_TOOLTIP).should( 'have.text', @@ -110,11 +112,8 @@ describe('Timelines', (): void => { }); describe('Creates a timeline by clicking untitled timeline from bottom bar', () => { - before(() => { - login(); - }); - beforeEach(() => { + login(); visit(OVERVIEW_URL); openTimelineUsingToggle(); addNameAndDescriptionToTimeline(getTimeline()); @@ -192,7 +191,9 @@ describe('Timelines', (): void => { cy.intercept('PATCH', '/api/timeline').as('updateTimeline'); cy.get(TIMELINE_CORRELATION_INPUT).should('be.visible'); waitForTimelineChanges(); - cy.get(TIMELINE_CORRELATION_INPUT).type('{selectAll} {del}').clear(); + cy.get(TIMELINE_CORRELATION_INPUT).type('{selectAll} {del}'); + cy.get(TIMELINE_CORRELATION_INPUT).clear(); + // TODO: It may need a further refactor to handle the frequency with which react calls this api // Since it's based on real time text changes...and real time query validation // there's almost no guarantee on the number of calls, so a cypress.wait may actually be more appropriate diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/data_providers.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/data_providers.cy.ts index eb0490212ffbbc..f1bee2d9190ef2 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/data_providers.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/data_providers.cy.ts @@ -32,10 +32,10 @@ import { cleanKibana, scrollToBottom } from '../../../tasks/common'; describe('timeline data providers', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); visit(HOSTS_URL); waitForAllHostsToBeLoaded(); scrollToBottom(); @@ -44,26 +44,22 @@ describe('timeline data providers', () => { populateTimeline(); }); - it('displays the data provider action menu when Enter is pressed', (done) => { - addDataProvider({ field: 'host.name', operator: 'exists' }).then(() => { - cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('not.exist'); - cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`) - .pipe(($el) => $el.trigger('focus')) - .should('exist'); - cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`) - .first() - .parent() - .type('{enter}'); + it('displays the data provider action menu when Enter is pressed', () => { + addDataProvider({ field: 'host.name', operator: 'exists' }); - cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('exist'); - done(); - }); + cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('not.exist'); + cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`).focus(); + cy.get(`${TIMELINE_FLYOUT_HEADER} ${TIMELINE_DROPPED_DATA_PROVIDERS}`) + .first() + .parent() + .type('{enter}'); + + cy.get(TIMELINE_DATA_PROVIDERS_ACTION_MENU).should('exist'); }); it('persists timeline when data provider is updated by dragging a field from data grid', () => { updateDataProviderbyDraggingField('host.name', 0); waitForTimelineChanges(); - cy.wait(1000); cy.reload(); cy.get(`${GET_TIMELINE_GRID_CELL('host.name')}`) .first() @@ -72,10 +68,11 @@ describe('timeline data providers', () => { }); }); - it('presists timeline when a field is added by hover action "Add To Timeline" in data provider ', () => { + it('persists timeline when a field is added by hover action "Add To Timeline" in data provider ', () => { + addDataProvider({ field: 'host.name', operator: 'exists' }); + waitForTimelineChanges(); updateDataProviderByFieldHoverAction('host.name', 0); waitForTimelineChanges(); - cy.wait(1000); cy.reload(); cy.get(`${GET_TIMELINE_GRID_CELL('host.name')}`) .first() diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/export.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/export.cy.ts index bc1f2f08122d22..aa36ef1a4f4582 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/export.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/export.cy.ts @@ -39,6 +39,11 @@ describe('Export timelines', () => { visitWithoutDateRange(TIMELINES_URL); }); + beforeEach(() => { + login(); + visitWithoutDateRange(TIMELINES_URL); + }); + it('Exports custom timeline(s)', function () { cy.log('Export a custom timeline via timeline actions'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/fields_browser.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/fields_browser.cy.ts index 0e94963fa02cef..d13c21325dfdfb 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/fields_browser.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/fields_browser.cy.ts @@ -52,10 +52,11 @@ const defaultHeaders = [ describe('Fields Browser', () => { before(() => { cleanKibana(); - login(); }); + context('Fields Browser rendering', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); @@ -122,6 +123,7 @@ describe('Fields Browser', () => { context('Editing the timeline', () => { beforeEach(() => { + login(); visit(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/flyout_button.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/flyout_button.cy.ts index 6512cb75019d2d..e4cfa5e4b80f2b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/flyout_button.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/flyout_button.cy.ts @@ -26,10 +26,10 @@ import { HOSTS_URL } from '../../../urls/navigation'; describe('timeline flyout button', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); visit(HOSTS_URL); waitForAllHostsToBeLoaded(); }); @@ -63,10 +63,8 @@ describe('timeline flyout button', () => { it('the `(+)` button popover menu owns focus when open', () => { openCreateTimelineOptionsPopover(); - cy.get(`${CREATE_NEW_TIMELINE}`) - .should('be.visible') - .pipe(($el) => $el.trigger('focus')) - .should('have.focus'); + cy.get(CREATE_NEW_TIMELINE).focus(); + cy.get(CREATE_NEW_TIMELINE).should('have.focus'); closeCreateTimelineOptionsPopover(); cy.get(CREATE_NEW_TIMELINE).should('not.exist'); }); @@ -76,7 +74,7 @@ describe('timeline flyout button', () => { cy.get('[data-test-subj="nav-search-input"]').focus(); cy.get('[data-test-subj="nav-search-input"]').should('be.focused'); cy.get('[data-test-subj="nav-search-option"]').should('be.visible'); - cy.get('[data-test-subj="nav-search-option"]').first().trigger('mouseenter'); + cy.get('[data-test-subj="nav-search-option"]').first().realHover(); // check that at least one item is visible in the search bar after mousing over, i.e. it's still usable. cy.get('[data-test-subj="nav-search-option"]').its('length').should('be.gte', 1); closeTimelineUsingCloseButton(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/full_screen.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/full_screen.cy.ts index 4cb08150ae67ed..56778ddc6bd6e7 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/full_screen.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/full_screen.cy.ts @@ -21,10 +21,10 @@ import { HOSTS_URL } from '../../../urls/navigation'; describe('Toggle full screen', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); visit(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/notes_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/notes_tab.cy.ts index ba0191b8080f0d..1196555a16e7d8 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/notes_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/notes_tab.cy.ts @@ -32,7 +32,7 @@ import { import { TIMELINES_URL } from '../../../urls/navigation'; -const text = 'elastic'; +const text = 'system_indices_superuser'; const link = 'https://www.elastic.co/'; describe('Timeline notes tab', () => { @@ -52,10 +52,12 @@ describe('Timeline notes tab', () => { }); beforeEach(function () { + login(); visitWithoutDateRange(TIMELINES_URL); - openTimelineById(this?.timelineId as string) - .then(() => goToNotesTab()) - .then(() => cy.wait(1000)); + openTimelineById(this?.timelineId as string); + goToNotesTab(); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(1000); }); it('should render mockdown', () => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/open_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/open_timeline.cy.ts index 09dad3a9ae040d..da6215d26796c9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/open_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/open_timeline.cy.ts @@ -48,6 +48,7 @@ describe('Open timeline', () => { // This cy.wait is here because we cannot do a pipe on a timeline as that will introduce multiple URL // request responses and indeterminism since on clicks to activates URL's. .then(() => cy.wrap(timelineId).as('timelineId')) + // eslint-disable-next-line cypress/no-unnecessary-waiting .then(() => cy.wait(1000)) .then(() => addNoteToTimeline(getTimeline().notes, timelineId).should((response) => @@ -62,6 +63,7 @@ describe('Open timeline', () => { describe('Open timeline modal', () => { beforeEach(function () { + login(); visitWithoutDateRange(TIMELINES_URL); openTimelineFromSettings(); openTimelineById(this.timelineId); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/overview.cy.tsx b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/overview.cy.tsx index d1a595df326315..7c2fd9ba060205 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/overview.cy.tsx +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/overview.cy.tsx @@ -32,11 +32,10 @@ describe('timeline overview search', () => { .then((response) => response.body.data.persistTimeline.timeline.savedObjectId) .then((timelineId) => favoriteTimeline({ timelineId, timelineType: 'default' })); createTimeline(getTimeline()); - - login(); }); beforeEach(() => { + login(); visitWithoutDateRange(TIMELINES_URL); cy.get(TIMELINES_OVERVIEW_SEARCH).clear(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/pagination.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/pagination.cy.ts index 07fc4412ebfd34..8446d25beb5411 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/pagination.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/pagination.cy.ts @@ -28,10 +28,10 @@ describe('Pagination', () => { before(() => { cleanKibana(); esArchiverLoad('timeline'); - login(); }); beforeEach(() => { + login(); visit(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/query_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/query_tab.cy.ts index 3321bbd184607a..0d509063c6ff05 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/query_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/query_tab.cy.ts @@ -42,6 +42,7 @@ describe('Timeline query tab', () => { // This cy.wait is here because we cannot do a pipe on a timeline as that will introduce multiple URL // request responses and indeterminism since on clicks to activates URL's. .then(() => cy.wrap(timelineId).as('timelineId')) + // eslint-disable-next-line cypress/no-unnecessary-waiting .then(() => cy.wait(1000)) .then(() => addNoteToTimeline(getTimeline().notes, timelineId).should((response) => @@ -57,6 +58,7 @@ describe('Timeline query tab', () => { describe('Query tab', () => { beforeEach(function () { + login(); visitWithoutDateRange(TIMELINES_URL); openTimelineById(this.timelineId).then(() => addFilter(getTimeline().filter)); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/row_renderers.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/row_renderers.cy.ts index 212ec9a571c12f..9a0a07bb97f064 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/row_renderers.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/row_renderers.cy.ts @@ -8,7 +8,6 @@ import { elementsOverlap } from '../../../helpers/rules'; import { TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN, - TIMELINE_ROW_RENDERERS_MODAL_CLOSE_BUTTON, TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX, TIMELINE_ROW_RENDERERS_SEARCHBOX, TIMELINE_SHOW_ROW_RENDERERS_GEAR, @@ -27,10 +26,11 @@ import { HOSTS_URL } from '../../../urls/navigation'; describe('Row renderers', () => { before(() => { cleanKibana(); - login(); }); + beforeEach(() => { deleteTimelines(); + login(); visit(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); @@ -38,10 +38,6 @@ describe('Row renderers', () => { cy.get(TIMELINE_SHOW_ROW_RENDERERS_GEAR).first().click({ force: true }); }); - afterEach(() => { - cy.get(TIMELINE_ROW_RENDERERS_MODAL_CLOSE_BUTTON).click({ force: true }); - }); - it('Row renderers should be enabled by default', () => { cy.get(TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX).should('exist'); cy.get(TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX).should('be.checked'); @@ -80,12 +76,9 @@ describe('Row renderers', () => { // In cases where the click handler is not present on the page just yet, this will cause the button to be clicked // multiple times until it sees that the click took effect. You could go through the whole list but I just check // for the first to be unchecked and then assume the click was successful - cy.root() - .pipe(($el) => { - $el.find(TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN).trigger('click'); - return $el.find(TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX).first(); - }) - .should('not.be.checked'); + cy.get(TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN).click(); + + cy.get(TIMELINE_ROW_RENDERERS_MODAL_ITEMS_CHECKBOX).first().should('not.be.checked'); cy.wait('@updateTimeline').its('response.statusCode').should('eq', 200); }); @@ -95,9 +88,7 @@ describe('Row renderers', () => { // A follw-up ticket to tackle this issue has been created. it.skip('Signature tooltips do not overlap', () => { // Hover the signature to show the tooltips - cy.get(TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE) - .parents('.euiPopover__anchor') - .trigger('mouseover'); + cy.get(TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE).parents('.euiPopover__anchor').realHover(); cy.get(TIMELINE_ROW_RENDERERS_SURICATA_LINK_TOOLTIP).then(($googleLinkTooltip) => { cy.get(TIMELINE_ROW_RENDERERS_SURICATA_SIGNATURE_TOOLTIP).then(($signatureTooltip) => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/search_or_filter.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/search_or_filter.cy.ts index ad8a21f904f2cc..eb72baca67af18 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/search_or_filter.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/search_or_filter.cy.ts @@ -12,6 +12,7 @@ import { TIMELINE_KQLMODE_SEARCH, TIMELINE_SEARCH_OR_FILTER, } from '../../../screens/timeline'; +import { LOADING_INDICATOR } from '../../../screens/security_header'; import { cleanKibana } from '../../../tasks/common'; import { login, visit, visitWithoutDateRange } from '../../../tasks/login'; @@ -28,11 +29,11 @@ import { HOSTS_URL, TIMELINES_URL } from '../../../urls/navigation'; describe('Timeline search and filters', () => { before(() => { cleanKibana(); - login(); }); describe('timeline search or filter KQL bar', () => { beforeEach(() => { + login(); visit(HOSTS_URL); }); @@ -56,14 +57,14 @@ describe('Timeline search and filters', () => { describe('Update kqlMode for timeline', () => { beforeEach(() => { + login(); visitWithoutDateRange(TIMELINES_URL); waitForTimelinesPanelToBeLoaded(); openTimelineUsingToggle(); cy.intercept('PATCH', '/api/timeline').as('update'); - cy.get('[data-test-subj="globalLoadingIndicator"]').should('not.exist'); - cy.get(TIMELINE_SEARCH_OR_FILTER) - .pipe(($el) => $el.trigger('click')) - .should('exist'); + cy.get(LOADING_INDICATOR).should('not.exist'); + cy.get(TIMELINE_SEARCH_OR_FILTER).click(); + cy.get(TIMELINE_SEARCH_OR_FILTER).should('exist'); }); it('should be able to update timeline kqlMode with filter', () => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/toggle_column.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/toggle_column.cy.ts index f69093949b92b9..7ee011ca931f41 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/toggle_column.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/toggle_column.cy.ts @@ -12,8 +12,6 @@ import { login, visit } from '../../../tasks/login'; import { openTimelineUsingToggle } from '../../../tasks/security_main'; import { clickIdToggleField, - closeTimeline, - createNewTimeline, expandFirstTimelineEventDetails, populateTimeline, clickTimestampToggleField, @@ -25,20 +23,15 @@ describe('toggle column in timeline', () => { before(() => { cleanKibana(); cy.intercept('POST', '/api/timeline/_export?file_name=timelines_export.ndjson').as('export'); - login(); }); beforeEach(() => { + login(); visit(HOSTS_URL); openTimelineUsingToggle(); populateTimeline(); }); - afterEach(() => { - createNewTimeline(); - closeTimeline(); - }); - it('removes the @timestamp field from the timeline when the user un-checks the toggle', () => { expandFirstTimelineEventDetails(); clickTimestampToggleField(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/unsaved_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/unsaved_timeline.cy.ts index 9e1fa076828714..bc3025914a68a9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/unsaved_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/timelines/unsaved_timeline.cy.ts @@ -49,6 +49,7 @@ describe('Save Timeline Prompts', () => { }); beforeEach(() => { + login(); visit(HOSTS_URL); createNewTimeline(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/ml/ml_conditional_links.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/ml/ml_conditional_links.cy.ts index b288ecc537e999..40caf2ae4ed8ec 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/ml/ml_conditional_links.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/ml/ml_conditional_links.cy.ts @@ -26,7 +26,7 @@ import { } from '../../urls/ml_conditional_links'; describe('ml conditional links', () => { - before(() => { + beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/network/hover_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/network/hover_actions.cy.ts index 415fb22e8ed28c..26dd7e08ae75f8 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/network/hover_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/network/hover_actions.cy.ts @@ -32,7 +32,6 @@ describe('Hover actions', () => { before(() => { esArchiverLoad('network'); - login(); }); after(() => { @@ -40,6 +39,7 @@ describe('Hover actions', () => { }); beforeEach(() => { + login(); visit(NETWORK_URL, { onBeforeLoad: onBeforeLoadCallback }); openHoverActions(); mouseoverOnToOverflowItem(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/network/overflow_items.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/network/overflow_items.cy.ts index 961e86308e3e6e..1a4f7b576c2e8c 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/network/overflow_items.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/network/overflow_items.cy.ts @@ -27,10 +27,10 @@ describe('Overflow items', () => { context('Network stats and tables', () => { before(() => { esArchiverLoad('network'); - login(); }); beforeEach(() => { + login(); visit(NETWORK_URL); cy.get(DESTINATION_DOMAIN).should('not.exist'); cy.get(FILTER_IN).should('not.exist'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/overview/cti_link_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/overview/cti_link_panel.cy.ts index f1c8b8d5d35963..c61222fc21b5ea 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/overview/cti_link_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/overview/cti_link_panel.cy.ts @@ -17,7 +17,7 @@ import { OVERVIEW_URL } from '../../urls/navigation'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; describe('CTI Link Panel', () => { - before(() => { + beforeEach(() => { login(); }); @@ -36,6 +36,10 @@ describe('CTI Link Panel', () => { esArchiverLoad('threat_indicator'); }); + beforeEach(() => { + login(); + }); + after(() => { esArchiverUnload('threat_indicator'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/overview/overview.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/overview/overview.cy.ts index 0bfd1cc88cec7f..14a67f3a528990 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/overview/overview.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/overview/overview.cy.ts @@ -17,10 +17,6 @@ import { createTimeline, favoriteTimeline } from '../../tasks/api_calls/timeline import { getTimeline } from '../../objects/timeline'; import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; -before(() => { - login(); -}); - describe('Overview Page', () => { before(() => { cleanKibana(); @@ -28,6 +24,7 @@ describe('Overview Page', () => { }); beforeEach(() => { + login(); visit(OVERVIEW_URL); }); @@ -77,6 +74,7 @@ describe('Overview page with no data', () => { }); it('Splash screen should be here', () => { + login(); visit(OVERVIEW_URL); cy.get(OVERVIEW_EMPTY_PAGE).should('be.visible'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/pagination/pagination.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/pagination/pagination.cy.ts index 506dcee9e66121..00754919ecf9c9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/pagination/pagination.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/pagination/pagination.cy.ts @@ -22,16 +22,13 @@ import { ALL_USERS_TABLE } from '../../screens/users/all_users'; import { goToTablePage, sortFirstTableColumn } from '../../tasks/table_pagination'; describe('Pagination', () => { - before(() => { - login(); - }); - describe('Host uncommon processes table)', () => { before(() => { esArchiverLoad('host_uncommon_processes'); }); beforeEach(() => { + login(); visit(HOSTS_PAGE_TAB_URLS.uncommonProcesses); waitForUncommonProcessesToBeLoaded(); }); @@ -106,6 +103,10 @@ describe('Pagination', () => { esArchiverLoad('all_users'); }); + beforeEach(() => { + login(); + }); + after(() => { esArchiverUnload('all_users'); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/urls/compatibility.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/compatibility.cy.ts index ba3772c3b0d3ac..9e522baf4a4b19 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/urls/compatibility.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/urls/compatibility.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ROLES } from '../../../common/test'; import { login, visit, visitWithoutDateRange } from '../../tasks/login'; import { @@ -35,6 +36,11 @@ const RULE_ID = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; describe('URL compatibility', () => { before(() => { + login(ROLES.platform_engineer); + visit(SECURITY_DETECTIONS_URL); + }); + + beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/urls/not_found.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/not_found.cy.ts index 4dc7d5231dcdd9..0f1b0644030253 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/urls/not_found.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/urls/not_found.cy.ts @@ -25,7 +25,7 @@ import { NOT_FOUND } from '../../screens/common/page'; const mockRuleId = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; describe('Display not found page', () => { - before(() => { + beforeEach(() => { login(); visit(TIMELINES_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts index b10309a366cc73..a87d37dc6e4fa0 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/urls/state.cy.ts @@ -20,6 +20,7 @@ import { HOSTS, KQL_INPUT, NETWORK, + LOADING_INDICATOR, openNavigationPanel, } from '../../screens/security_header'; import { TIMELINE_TITLE } from '../../screens/timeline'; @@ -70,7 +71,7 @@ const ABSOLUTE_DATE = { }; describe('url state', () => { - before(() => { + beforeEach(() => { login(); }); @@ -286,14 +287,14 @@ describe('url state', () => { populateTimeline(); cy.intercept('PATCH', '/api/timeline').as('timeline'); - cy.get('[data-test-subj="globalLoadingIndicator"]').should('not.exist'); + cy.get(LOADING_INDICATOR).should('not.exist'); cy.wait('@timeline').then(({ response }) => { addNameToTimeline(getTimeline().title); closeTimeline(); cy.wrap(response?.statusCode).should('eql', 200); const timelineId = response?.body.data.persistTimeline.timeline.savedObjectId; - cy.visit('/app/home'); - cy.visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`); + visit('/app/home'); + visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`); cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('exist'); cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE).should('not.have.text', 'Updating'); cy.get(TIMELINE).should('be.visible'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/users/user_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/users/user_details.cy.ts index 3a1fa8e2516bdc..15d29f586d2c4d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/users/user_details.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/users/user_details.cy.ts @@ -21,7 +21,7 @@ import { import { USER_COLUMN } from '../../screens/alerts'; describe('user details flyout', () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/users/users_tabs.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/users/users_tabs.cy.ts index 3cfc225a4b7d27..37ad052e9baf88 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/users/users_tabs.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/users/users_tabs.cy.ts @@ -26,10 +26,10 @@ describe('Users stats and tables', () => { esArchiverLoad('users'); esArchiverLoad('risk_users'); - login(); }); beforeEach(() => { + login(); visit(USERS_URL); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/value_lists/value_lists.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/value_lists/value_lists.cy.ts index e3bae4b771fbf8..719310b80a7eb1 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/value_lists/value_lists.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/value_lists/value_lists.cy.ts @@ -30,10 +30,8 @@ import { describe('value lists', () => { describe('management modal', () => { - before(() => { - login(); - }); beforeEach(() => { + login(); createListsIndex(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); waitForListsIndex(); diff --git a/x-pack/plugins/security_solution/cypress/helpers/common.ts b/x-pack/plugins/security_solution/cypress/helpers/common.ts index 82a8c711d7cc9e..26b23010d6e48c 100644 --- a/x-pack/plugins/security_solution/cypress/helpers/common.ts +++ b/x-pack/plugins/security_solution/cypress/helpers/common.ts @@ -27,7 +27,7 @@ export const getClassSelector = (className: string) => `.${className}`; export const getLocalstorageEntryAsObject = (storage: Cypress.StorageByOrigin, field: string) => { // baseUrl value from x-pack/plugins/security_solution/cypress/cypress.config.ts - const envLocalstorage = storage?.['http://localhost:5620']; + const envLocalstorage = storage?.[Cypress.config('baseUrl') as string]; let result; if (envLocalstorage && envLocalstorage[field]) { try { diff --git a/x-pack/plugins/security_solution/cypress/objects/exception.ts b/x-pack/plugins/security_solution/cypress/objects/exception.ts index d95455e34dc7e1..cae710b4bd8468 100644 --- a/x-pack/plugins/security_solution/cypress/objects/exception.ts +++ b/x-pack/plugins/security_solution/cypress/objects/exception.ts @@ -61,5 +61,5 @@ export const expectedExportedExceptionList = ( exceptionListResponse: Cypress.Response ): string => { const jsonrule = exceptionListResponse.body; - return `{"_version":"${jsonrule._version}","created_at":"${jsonrule.created_at}","created_by":"elastic","description":"${jsonrule.description}","id":"${jsonrule.id}","immutable":false,"list_id":"${jsonrule.list_id}","name":"${jsonrule.name}","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonrule.tie_breaker_id}","type":"${jsonrule.type}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","version":1}\n{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; + return `{"_version":"${jsonrule._version}","created_at":"${jsonrule.created_at}","created_by":"system_indices_superuser","description":"${jsonrule.description}","id":"${jsonrule.id}","immutable":false,"list_id":"${jsonrule.list_id}","name":"${jsonrule.name}","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonrule.tie_breaker_id}","type":"${jsonrule.type}","updated_at":"${jsonrule.updated_at}","updated_by":"system_indices_superuser","version":1}\n{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; }; diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts index 86ac59950e5fb4..745091280e8791 100644 --- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts @@ -108,9 +108,9 @@ export const expectedExportedTimelineTemplate = ( templateTimelineVersion: 1, timelineType: 'template', created: timelineTemplateBody.created, - createdBy: 'elastic', + createdBy: 'system_indices_superuser', updated: timelineTemplateBody.updated, - updatedBy: 'elastic', + updatedBy: 'system_indices_superuser', sort: [], eventNotes: [], globalNotes: [], @@ -143,9 +143,9 @@ export const expectedExportedTimeline = (timelineResponse: Cypress.Response