diff --git a/.buildkite/pipelines/chrome_forward_testing.yml b/.buildkite/pipelines/chrome_forward_testing.yml index eb80625a73d77..965f910e8e07e 100644 --- a/.buildkite/pipelines/chrome_forward_testing.yml +++ b/.buildkite/pipelines/chrome_forward_testing.yml @@ -20,9 +20,9 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml b/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml index fa8162ca93c0e..3058293bae793 100644 --- a/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml +++ b/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml @@ -31,13 +31,13 @@ steps: provider: gcp machineType: n2-standard-2 - - label: "Build Kibana Distribution and Plugins" + - label: "Build Kibana Distribution" command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" depends_on: pre-build diff --git a/.buildkite/pipelines/es_snapshots/verify.yml b/.buildkite/pipelines/es_snapshots/verify.yml index d91af527c8266..e8c9899e9aed7 100755 --- a/.buildkite/pipelines/es_snapshots/verify.yml +++ b/.buildkite/pipelines/es_snapshots/verify.yml @@ -23,12 +23,12 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/fips.yml b/.buildkite/pipelines/fips.yml index 09ae10496456f..f4a5b3623bbcc 100644 --- a/.buildkite/pipelines/fips.yml +++ b/.buildkite/pipelines/fips.yml @@ -17,9 +17,9 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/flaky_tests/pipeline.ts b/.buildkite/pipelines/flaky_tests/pipeline.ts index ec1b4994beae3..0d6c3b5c40f7d 100644 --- a/.buildkite/pipelines/flaky_tests/pipeline.ts +++ b/.buildkite/pipelines/flaky_tests/pipeline.ts @@ -122,7 +122,7 @@ const pipeline = { steps.push({ command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', + label: 'Build Kibana Distribution', agents: expandAgentQueue('c2-8'), key: 'build', if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''", diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 65c7f0095f063..e5d28465b2dc3 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -17,12 +17,12 @@ steps: - wait - command: .buildkite/scripts/steps/on_merge_build_and_metrics.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml index cf1a523be5a32..a7800fcc92dce 100644 --- a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml +++ b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml @@ -13,12 +13,12 @@ steps: limit: 1 - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/performance/daily.yml b/.buildkite/pipelines/performance/daily.yml index bbbdde955df76..1eeb044304d22 100644 --- a/.buildkite/pipelines/performance/daily.yml +++ b/.buildkite/pipelines/performance/daily.yml @@ -9,13 +9,13 @@ steps: - wait - - label: '🧑‍🏭 Build Kibana Distribution and Plugins' + - label: '🧑‍🏭 Build Kibana Distribution' command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: c2-standard-16 + machineType: n2-standard-8 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/performance/data_set_extraction_daily.yml b/.buildkite/pipelines/performance/data_set_extraction_daily.yml index bb2c33f7a2d33..dcb7355318960 100644 --- a/.buildkite/pipelines/performance/data_set_extraction_daily.yml +++ b/.buildkite/pipelines/performance/data_set_extraction_daily.yml @@ -10,13 +10,13 @@ steps: - wait - - label: ':building_construction: Build Kibana Distribution and Plugins' + - label: ':building_construction: Build Kibana Distribution ' command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: c2-standard-16 + machineType: n2-standard-8 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index e7b593f464b54..f56159882b2af 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -8,9 +8,9 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml b/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml index 785726bf95174..909329234c328 100644 --- a/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml +++ b/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml @@ -9,13 +9,13 @@ steps: - wait - - label: 'Build Kibana Distribution and Plugins' + - label: 'Build Kibana Distribution' command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/scripts/build_kibana_plugins.sh b/.buildkite/scripts/build_kibana_plugins.sh deleted file mode 100755 index 4e566008b3351..0000000000000 --- a/.buildkite/scripts/build_kibana_plugins.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -echo "--- Build Platform Plugins" -THREADS=$(grep -c ^processor /proc/cpuinfo) -node scripts/build_kibana_platform_plugins --examples --test-plugins --workers "$THREADS" --no-inspect-workers --no-progress diff --git a/.buildkite/scripts/download_build_artifacts.sh b/.buildkite/scripts/download_build_artifacts.sh index 1b52e82c2d66b..497efa181018e 100755 --- a/.buildkite/scripts/download_build_artifacts.sh +++ b/.buildkite/scripts/download_build_artifacts.sh @@ -6,12 +6,11 @@ source "$(dirname "$0")/common/util.sh" if [[ "${KIBANA_BUILD_ID:-}" != "false" ]]; then if [[ ! -d "$KIBANA_BUILD_LOCATION/bin" ]]; then - echo '--- Downloading Distribution and Plugin artifacts' + echo '--- Downloading Distribution' cd "$WORKSPACE" download_artifact kibana-default.tar.gz . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" - download_artifact kibana-default-plugins.tar.gz . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" mkdir -p "$KIBANA_BUILD_LOCATION" tar -xzf kibana-default.tar.gz -C "$KIBANA_BUILD_LOCATION" --strip=1 @@ -24,7 +23,5 @@ if [[ "${KIBANA_BUILD_ID:-}" != "false" ]]; then fi cd "$KIBANA_DIR" - - tar -xzf ../kibana-default-plugins.tar.gz fi fi diff --git a/.buildkite/scripts/post_build_kibana_plugins.sh b/.buildkite/scripts/post_build_kibana_plugins.sh deleted file mode 100755 index 288fd262cafa9..0000000000000 --- a/.buildkite/scripts/post_build_kibana_plugins.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -echo "--- Archive built plugins" -shopt -s globstar -tar -zcf \ - target/kibana-default-plugins.tar.gz \ - x-pack/plugins/**/target/public \ - x-pack/test/**/target/public \ - examples/**/target/public \ - x-pack/examples/**/target/public \ - test/**/target/public diff --git a/.buildkite/scripts/steps/build_kibana.sh b/.buildkite/scripts/steps/build_kibana.sh index bf4a9ff243d02..4bad332dd073f 100755 --- a/.buildkite/scripts/steps/build_kibana.sh +++ b/.buildkite/scripts/steps/build_kibana.sh @@ -4,6 +4,4 @@ set -euo pipefail .buildkite/scripts/bootstrap.sh .buildkite/scripts/build_kibana.sh -.buildkite/scripts/build_kibana_plugins.sh -.buildkite/scripts/post_build_kibana_plugins.sh .buildkite/scripts/post_build_kibana.sh diff --git a/.buildkite/scripts/steps/functional/defend_workflows_burn.sh b/.buildkite/scripts/steps/functional/defend_workflows_burn.sh index 6a97ba4e82b33..44d87ba4a6635 100644 --- a/.buildkite/scripts/steps/functional/defend_workflows_burn.sh +++ b/.buildkite/scripts/steps/functional/defend_workflows_burn.sh @@ -4,10 +4,7 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh -.buildkite/scripts/bootstrap.sh -.buildkite/scripts/copy_es_snapshot_cache.sh -node scripts/build_kibana_platform_plugins.js - +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} export JOB=kibana-defend-workflows-cypress buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" 'false' diff --git a/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh b/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh index 4bebee15953e6..a092ffbd4186b 100644 --- a/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh +++ b/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh @@ -4,10 +4,7 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh -.buildkite/scripts/bootstrap.sh -.buildkite/scripts/copy_es_snapshot_cache.sh -node scripts/build_kibana_platform_plugins.js - +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} export JOB=kibana-defend-workflows-serverless-cypress buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" 'false' diff --git a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh index fb05bb99b0c54..1f1e492f87bec 100755 --- a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh +++ b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh @@ -4,7 +4,5 @@ set -euo pipefail .buildkite/scripts/bootstrap.sh .buildkite/scripts/build_kibana.sh -.buildkite/scripts/build_kibana_plugins.sh -.buildkite/scripts/post_build_kibana_plugins.sh .buildkite/scripts/post_build_kibana.sh .buildkite/scripts/saved_object_field_metrics.sh diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 22d5aaa2877c3..f030de4645e3f 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -11,6 +11,7 @@ Review important information about the {kib} 8.x releases. * <> +* <> * <> * <> * <> @@ -430,6 +431,33 @@ Management:: * Fixes the pagination of the source documents data grid in Transforms ({kibana-pull}196119[#196119]). * Fixes autocomplete suggestions after a comma in Console ({kibana-pull}189656[#189656]). +[[release-notes-8.15.4]] +== {kib} 8.15.4 + +The 8.15.4 release includes the following bug fixes. + +[float] +[[fixes-v8.15.4]] +=== Bug fixes +Dashboards and visualizations:: +* Fixes incomplete string escaping and encoding in *TSVB* ({kibana-pull}196248[#196248]). +* Adds scroll margin to panels ({kibana-pull}193430[#193430]). +* Fixes an issue where label truncation in heat map legends was not working properly in *Lens* ({kibana-pull}195928[#195928]). +Discover:: +* Fixes the width for saved object Type column ({kibana-pull}194388[#194388]). +Elastic Observability solution:: +* Changes the slice outcome from bad to good whenever there is no data during the slice window ({kibana-pull}196942[#196942]). +Elastic Search solution:: +* Fixes a bug with the {ref}/es-connectors-network-drive.html[Network Drive connector] where advanced configuration fields were not displayed for CSV file role mappings with `Drive Type: Linux` selected ({kibana-pull}195567[#195567]). +Elastic Security solution:: +For the Elastic Security 8.15.4 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Kibana platform:: +* Fixes an issue causing the wrong date to show in the header of a report when generated from a relative date ({kibana-pull}197027[#197027]). +* Fixes an issue with the export options for PNG/PDF reports in a dashboard ({kibana-pull}192530[#192530]). +Machine Learning:: +* Fixes an issue preventing Anomaly swim lane panels from updating on query changes ({kibana-pull}195090[#195090]). +Management:: +* Fixes the pagination of the source documents data grid in Transforms ({kibana-pull}196119[#196119]). [[release-notes-8.15.3]] == {kib} 8.15.3 @@ -456,7 +484,6 @@ Elastic Security solution:: For the Elastic Security 8.15.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. Kibana security:: * Automatic Import no longer asks the LLM to map fields to reserved ECS fields ({kibana-pull}195168[#195168]). -* Automatic Import no longer returns an "Invalid ECS field" message when the ECS mapping slightly differs from the expected format. For example `date_format` instead of `date_formats` ({kibana-pull}195167[#195167]). * Fixes an issue that was causing the Grok processor to return non-ECS compatible fields when processing structured or unstructured syslog samples in Automatic Import ({kibana-pull}194727[#194727]). * Fixes the integrationName when uploading a new version of an existing integration using a ZIP upload ({kibana-pull}194298[#194298]). * Fixes a bug that caused the Deploy step of Automatic Import to fail after a pipeline was edited and saved ({kibana-pull}194203[#194203]). diff --git a/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx b/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx index fbef21087cffd..de230ac617987 100644 --- a/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx +++ b/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx @@ -386,7 +386,7 @@ const ConfigPanelGenericSwitch = ({ value: boolean; onChange: (event: EuiSwitchEvent) => void; }) => ( - + = {}): CsvConfigType => ({ maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: true, + enablePanelActionDownload: false, maxConcurrentShardRequests: 5, ...opts, }); diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts index 4e85431448e45..9ae0b2b711c19 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts @@ -98,7 +98,7 @@ describe('CsvESQLGenerator', () => { maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: true, + enablePanelActionDownload: false, maxConcurrentShardRequests: 5, }; @@ -569,7 +569,7 @@ describe('CsvESQLGenerator', () => { maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: true, + enablePanelActionDownload: false, maxConcurrentShardRequests: 5, }; mockSearchResponse({ diff --git a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts index 9b14636081233..05a321aa1a255 100644 --- a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts +++ b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts @@ -39,7 +39,7 @@ describe('getExportSettings', () => { scroll: { size: 500, duration: '30s', strategy: 'pit' }, useByteOrderMarkEncoding: false, maxConcurrentShardRequests: 5, - enablePanelActionDownload: true, + enablePanelActionDownload: false, }; taskInstanceFields = { startedAt: null, retryAt: null }; diff --git a/packages/kbn-reporting/common/routes.ts b/packages/kbn-reporting/common/routes.ts index 77d126b94e08a..dc01746fcc8db 100644 --- a/packages/kbn-reporting/common/routes.ts +++ b/packages/kbn-reporting/common/routes.ts @@ -24,7 +24,6 @@ export const INTERNAL_ROUTES = { DELETE_PREFIX: prefixInternalPath + '/jobs/delete', // docId is added to the final path DOWNLOAD_PREFIX: prefixInternalPath + '/jobs/download', // docId is added to the final path }, - DOWNLOAD_CSV: prefixInternalPath + '/generate/immediate/csv_searchsource', // DEPRECATED GENERATE_PREFIX: prefixInternalPath + '/generate', // exportTypeId is added to the final path }; diff --git a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts b/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts deleted file mode 100644 index 417cc782a54cb..0000000000000 --- a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { CsvGenerator } from '@kbn/generate-csv'; - -jest.mock('@kbn/generate-csv', () => { - return { - CsvGenerator: jest.fn().mockImplementation(() => { - return { generateData: jest.fn() }; - }), - }; -}); - -import { httpServerMock } from '@kbn/core-http-server-mocks'; -import type { CoreStart, KibanaRequest } from '@kbn/core/server'; -import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; -import { discoverPluginMock } from '@kbn/discover-plugin/server/mocks'; -import { createFieldFormatsStartMock } from '@kbn/field-formats-plugin/server/mocks'; -import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; -import { setFieldFormats } from '@kbn/reporting-server'; -import type { Writable } from 'stream'; - -import { CsvSearchSourceImmediateExportType } from '.'; -import { ReportingRequestHandlerContext } from './types'; - -const mockLogger = loggingSystemMock.createLogger(); -const encryptionKey = 'tetkey'; -let stream: jest.Mocked; -let mockCsvSearchSourceImmediateExportType: CsvSearchSourceImmediateExportType; -let mockCoreStart: CoreStart; -let mockRequest: KibanaRequest; -let mockRequestHandlerContext: ReportingRequestHandlerContext; - -beforeEach(async () => { - // use fieldFormats plugin for csv formats - // normally, this is done in the Reporting plugin - setFieldFormats(createFieldFormatsStartMock()); - stream = {} as typeof stream; - - const configType = createMockConfigSchema({ - encryptionKey, - csv: { - checkForFormulas: true, - escapeFormulaValues: true, - maxSizeBytes: 180000, - scroll: { size: 500, duration: 'auto' }, - }, - }); - const mockCoreSetup = coreMock.createSetup(); - mockCoreStart = coreMock.createStart(); - const context = coreMock.createPluginInitializerContext(configType); - mockRequest = httpServerMock.createKibanaRequest(); - - mockCsvSearchSourceImmediateExportType = new CsvSearchSourceImmediateExportType( - mockCoreSetup, - configType, - mockLogger, - context - ); - - mockRequestHandlerContext = { - core: Promise.resolve(mockCoreStart), - } as unknown as ReportingRequestHandlerContext; - - mockCsvSearchSourceImmediateExportType.setup({ - basePath: { set: jest.fn() }, - }); - - mockCsvSearchSourceImmediateExportType.start({ - esClient: elasticsearchServiceMock.createClusterClient(), - savedObjects: mockCoreStart.savedObjects, - uiSettings: mockCoreStart.uiSettings, - discover: discoverPluginMock.createStartContract(), - data: dataPluginMock.createStartContract(), - }); - - jest.useFakeTimers(); - jest.setSystemTime(1630526670000); -}); - -afterEach(() => { - jest.useRealTimers(); -}); - -test('allows csv.scroll.duration to be "auto"', async () => { - const mockGenerateData = jest.fn().mockResolvedValue(() => ({ csv_contains_formulas: false })); - (CsvGenerator as jest.Mock).mockImplementationOnce(() => { - return { generateData: mockGenerateData }; - }); - - await mockCsvSearchSourceImmediateExportType.runTask( - 'cool-job-id', - { browserTimezone: 'US/Alaska', searchSource: {}, title: 'Test Search' }, - mockRequestHandlerContext, - stream, - mockRequest - ); - - expect(CsvGenerator).toBeCalledWith( - { - browserTimezone: 'US/Alaska', - objectType: 'immediate-search', - searchSource: {}, - title: 'Test Search', - }, - { - checkForFormulas: true, - escapeFormulaValues: true, - maxSizeBytes: 180000, - scroll: { duration: 'auto', size: 500 }, - }, - { - retryAt: new Date('2021-09-01T20:06:30.000Z'), - startedAt: new Date('2021-09-01T20:04:30.000Z'), - }, - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything() - ); - - expect(mockGenerateData).toBeCalled(); -}); diff --git a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts b/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts deleted file mode 100644 index 5ca85cb27e540..0000000000000 --- a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts +++ /dev/null @@ -1,157 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { Writable } from 'stream'; - -import { KibanaRequest } from '@kbn/core-http-server'; -import { DataPluginStart } from '@kbn/data-plugin/server/plugin'; -import { DiscoverServerPluginStart } from '@kbn/discover-plugin/server'; -import { CsvGenerator } from '@kbn/generate-csv'; -import { - CancellationToken, - LICENSE_TYPE_BASIC, - LICENSE_TYPE_CLOUD_STANDARD, - LICENSE_TYPE_ENTERPRISE, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_TRIAL, - durationToNumber, -} from '@kbn/reporting-common'; -import type { TaskRunResult } from '@kbn/reporting-common/types'; -import { - CSV_SEARCHSOURCE_IMMEDIATE_TYPE, - JobParamsDownloadCSV, -} from '@kbn/reporting-export-types-csv-common'; -import type { BaseExportTypeSetupDeps, BaseExportTypeStartDeps } from '@kbn/reporting-server'; -import { ExportType, getFieldFormats } from '@kbn/reporting-server'; - -import { ReportingRequestHandlerContext } from './types'; - -type CsvSearchSourceImmediateExportTypeSetupDeps = BaseExportTypeSetupDeps; -interface CsvSearchSourceImmediateExportTypeStartDeps extends BaseExportTypeStartDeps { - discover: DiscoverServerPluginStart; - data: DataPluginStart; -} - -/* - * ImmediateExecuteFn receives the job doc payload because the payload was - * generated in the ScheduleFn - */ -export type ImmediateExecuteFn = ( - jobId: null, - job: JobParamsDownloadCSV, - context: ReportingRequestHandlerContext, - stream: Writable, - req: KibanaRequest -) => Promise; - -/** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ -export class CsvSearchSourceImmediateExportType extends ExportType< - JobParamsDownloadCSV, - ImmediateExecuteFn, - CsvSearchSourceImmediateExportTypeSetupDeps, - CsvSearchSourceImmediateExportTypeStartDeps -> { - id = CSV_SEARCHSOURCE_IMMEDIATE_TYPE; - name = CSV_SEARCHSOURCE_IMMEDIATE_TYPE; - jobType = CSV_SEARCHSOURCE_IMMEDIATE_TYPE; - jobContentEncoding = 'base64' as const; - jobContentExtension = 'csv' as const; - validLicenses = [ - LICENSE_TYPE_TRIAL, - LICENSE_TYPE_BASIC, - LICENSE_TYPE_CLOUD_STANDARD, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_ENTERPRISE, - ]; - - constructor(...args: ConstructorParameters) { - super(...args); - this.logger = this.logger.get('csv-searchsource-export'); - } - - public createJob = async () => { - throw new Error(`immediate download has no create job handler!`); - }; - // @ts-ignore expected type failure from deprecated export type - public runTask = async ( - _jobId: string | null, - immediateJobParams: JobParamsDownloadCSV, - context: ReportingRequestHandlerContext, - stream: Writable, - req: KibanaRequest - ) => { - const job = { - objectType: 'immediate-search', - ...immediateJobParams, - }; - - const dataPluginStart = this.startDeps.data; - const savedObjectsClient = (await context.core).savedObjects.client; - const uiSettings = this.getUiSettingsServiceFactory(savedObjectsClient); - const fieldFormatsRegistry = await getFieldFormats().fieldFormatServiceFactory(uiSettings); - - const es = this.startDeps.esClient.asScoped(req); - const searchSourceStart = await dataPluginStart.search.searchSource.asScoped(req); - const clients = { - uiSettings, - data: dataPluginStart.search.asScoped(req), - es, - }; - const dependencies = { - fieldFormatsRegistry, - searchSourceStart, - }; - const cancellationToken = new CancellationToken(); - const csvConfig = this.config.csv; - const taskInstanceFields = - csvConfig.scroll.duration === 'auto' - ? { - startedAt: new Date(), - retryAt: new Date(Date.now() + durationToNumber(this.config.queue.timeout)), - } - : { - startedAt: null, - retryAt: null, - }; - - const csv = new CsvGenerator( - job, - csvConfig, - taskInstanceFields, - clients, - dependencies, - cancellationToken, - this.logger, - stream - ); - const result = await csv.generateData(); - - if (result.csv_contains_formulas) { - this.logger.warn(`CSV may contain formulas whose values have been escaped`); - } - - if (result.max_size_reached) { - this.logger.warn(`Max size reached: CSV output truncated`); - } - - const { warnings } = result; - if (warnings) { - warnings.forEach((warning) => { - this.logger.warn(warning); - }); - } - - return result; - }; -} diff --git a/packages/kbn-reporting/export_types/csv/index.ts b/packages/kbn-reporting/export_types/csv/index.ts index 5910d17926c06..8ad8d200f6b77 100644 --- a/packages/kbn-reporting/export_types/csv/index.ts +++ b/packages/kbn-reporting/export_types/csv/index.ts @@ -9,4 +9,3 @@ export { CsvSearchSourceExportType } from './csv_searchsource'; export { CsvV2ExportType } from './csv_v2'; -export { CsvSearchSourceImmediateExportType } from './csv_searchsource_immediate'; diff --git a/packages/kbn-reporting/export_types/csv/tsconfig.json b/packages/kbn-reporting/export_types/csv/tsconfig.json index 2fef9b63fda79..a5ee79e9b52c4 100644 --- a/packages/kbn-reporting/export_types/csv/tsconfig.json +++ b/packages/kbn-reporting/export_types/csv/tsconfig.json @@ -21,12 +21,10 @@ "@kbn/discover-plugin", "@kbn/data-plugin", "@kbn/generate-csv", - "@kbn/core-http-server", "@kbn/reporting-server", "@kbn/reporting-export-types-csv-common", "@kbn/reporting-mocks-server", "@kbn/core-http-request-handler-context-server", "@kbn/field-formats-plugin", - "@kbn/core-http-server-mocks", ] } diff --git a/packages/kbn-reporting/export_types/csv_common/index.ts b/packages/kbn-reporting/export_types/csv_common/index.ts index 1a2b175347484..259f4a917783a 100644 --- a/packages/kbn-reporting/export_types/csv_common/index.ts +++ b/packages/kbn-reporting/export_types/csv_common/index.ts @@ -18,17 +18,6 @@ import type { export * from './constants'; -/** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ -export interface JobParamsDownloadCSV { - browserTimezone: string; - title: string; - searchSource: SerializedSearchSourceFields; - columns?: string[]; -} - interface BaseParamsCSV { searchSource: SerializedSearchSourceFields; columns?: string[]; @@ -62,12 +51,6 @@ export interface TaskPayloadCsvFromSavedObject extends CsvFromSavedObjectBase, B export const CSV_REPORTING_ACTION = 'generateCsvReport'; -/** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ -export const CSV_SEARCHSOURCE_IMMEDIATE_TYPE = 'csv_searchsource_immediate'; - /** * @deprecated * Supported in case older reports exist in storage diff --git a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts index 72da136ba9223..6eaa3e933980b 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts @@ -61,7 +61,6 @@ describe('GetCsvReportPanelAction', () => { beforeEach(() => { csvConfig = { scroll: {} as ClientConfigType['csv']['scroll'], - enablePanelActionDownload: false, }; apiClient = new ReportingAPIClient(core.http, core.uiSettings, '7.15.0'); @@ -285,58 +284,4 @@ describe('GetCsvReportPanelAction', () => { expect(await plugin.isCompatible(context)).toEqual(true); }); }); - - describe('download csv', () => { - beforeEach(() => { - csvConfig = { - scroll: {} as ClientConfigType['csv']['scroll'], - enablePanelActionDownload: true, - }; - - core.http.post.mockResolvedValue({}); - }); - - it('shows a success toast when the download successfully starts', async () => { - const panel = new ReportingCsvPanelAction({ - core, - apiClient, - startServices$: mockStartServices$, - usesUiCapabilities: true, - csvConfig, - }); - - await Rx.firstValueFrom(mockStartServices$); - - await panel.execute(context); - - expect(core.notifications.toasts.addSuccess).toHaveBeenCalledWith({ - 'data-test-subj': 'csvDownloadStarted', - text: expect.any(Function), - title: 'CSV download started', - }); - expect(core.notifications.toasts.addDanger).not.toHaveBeenCalled(); - }); - - it('shows a bad old toastie when it unsuccessfully fails', async () => { - apiClient.createImmediateReport = jest.fn().mockRejectedValue('No more ram!'); - const panel = new ReportingCsvPanelAction({ - core, - apiClient, - startServices$: mockStartServices$, - usesUiCapabilities: true, - csvConfig, - }); - - await Rx.firstValueFrom(mockStartServices$); - - await panel.execute(context); - - expect(core.notifications.toasts.addSuccess).toHaveBeenCalled(); - expect(core.notifications.toasts.addDanger).toHaveBeenCalledWith({ - 'data-test-subj': 'downloadCsvFail', - text: "We couldn't download your CSV at this time.", - title: 'CSV download failed', - }); - }); - }); }); diff --git a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx index 7be14579523bd..65712496519f7 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx @@ -102,7 +102,6 @@ export class ReportingCsvPanelAction implements ActionDefinition; private readonly notifications: NotificationsSetup; private readonly apiClient: ReportingAPIClient; - private readonly enablePanelActionDownload: boolean; private readonly theme: ThemeServiceSetup; private readonly startServices$: Params['startServices$']; private readonly usesUiCapabilities: boolean; @@ -110,7 +109,6 @@ export class ReportingCsvPanelAction implements ActionDefinition { - const { searchSource, columns, title, analytics, i18nStart } = params; - const immediateJobParams = this.apiClient.getDecoratedJobParams({ - searchSource, - columns, - title, - objectType: 'downloadCsv', // FIXME: added for typescript, but immediate download job does not need objectType - }); - - this.isDownloading = true; - - this.notifications.toasts.addSuccess({ - title: this.i18nStrings.download.toasts.success.title, - text: toMountPoint(this.i18nStrings.download.toasts.success.body, { - analytics, - i18n: i18nStart, - theme: this.theme, - }), - 'data-test-subj': 'csvDownloadStarted', - }); - - await this.apiClient - .createImmediateReport(immediateJobParams) - .then(({ body, response }) => { - this.isDownloading = false; - - const download = `${title}.csv`; - const blob = new Blob([body as BlobPart], { - type: response?.headers.get('content-type') || undefined, - }); - - // Hack for IE11 Support - // @ts-expect-error - if (window.navigator.msSaveOrOpenBlob) { - // @ts-expect-error - return window.navigator.msSaveOrOpenBlob(blob, download); - } - - const a = window.document.createElement('a'); - const downloadObject = window.URL.createObjectURL(blob); - - a.href = downloadObject; - a.download = download; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(downloadObject); - document.body.removeChild(a); - }) - .catch((error: unknown) => { - // eslint-disable-next-line no-console - console.error(error); - this.isDownloading = false; - this.notifications.toasts.addDanger({ - title: this.i18nStrings.download.toasts.error.title, - text: this.i18nStrings.download.toasts.error.body, - 'data-test-subj': 'downloadCsvFail', - }); - }); - }; - private executeGenerate = async (params: ExecutionParams) => { const { searchSource, columns, title, analytics, i18nStart } = params; const csvJobParams = this.apiClient.getDecoratedJobParams({ @@ -278,9 +210,6 @@ export class ReportingCsvPanelAction implements ActionDefinition => ({ - download: { - displayName: i18n.translate('reporting.share.panelAction.downloadCsvPanelTitle', { - defaultMessage: 'Download CSV', - }), - toasts: { - error: { - title: i18n.translate('reporting.share.panelAction.failedCsvReportTitle', { - defaultMessage: `CSV download failed`, - }), - body: i18n.translate('reporting.share.panelAction.failedCsvReportMessage', { - defaultMessage: `We couldn't download your CSV at this time.`, - }), - }, - success: { - title: i18n.translate('reporting.share.panelAction.csvDownloadStartedTitle', { - defaultMessage: `CSV download started`, - }), - body: ( - - ), - }, - }, - }, +export const getI18nStrings = (apiClient: ReportingAPIClient): Record<'generate', I18nStrings> => ({ generate: { displayName: i18n.translate('reporting.share.panelAction.generateCsvPanelTitle', { defaultMessage: 'Generate CSV report', diff --git a/packages/kbn-reporting/public/reporting_api_client.test.ts b/packages/kbn-reporting/public/reporting_api_client.test.ts index c1da4b46ae27a..3504ed87956d4 100644 --- a/packages/kbn-reporting/public/reporting_api_client.test.ts +++ b/packages/kbn-reporting/public/reporting_api_client.test.ts @@ -214,32 +214,6 @@ describe('ReportingAPIClient', () => { }); }); - describe('createImmediateReport', () => { - beforeEach(() => { - httpClient.post.mockResolvedValueOnce({ job: { payload: {} } }); - }); - - it('should send a post request', async () => { - await apiClient.createImmediateReport({ - browserTimezone: 'UTC', - objectType: 'something', - title: 'some title', - version: 'some version', - }); - - expect(httpClient.post).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - body: JSON.stringify({ - browserTimezone: 'UTC', - title: 'some title', - version: 'some version', - }), - }) - ); - }); - }); - describe('getDecoratedJobParams', () => { beforeEach(() => { jest.spyOn(tz, 'guess').mockReturnValue('UTC'); diff --git a/packages/kbn-reporting/public/reporting_api_client.ts b/packages/kbn-reporting/public/reporting_api_client.ts index 48931fee1cf51..66f5daa0c520a 100644 --- a/packages/kbn-reporting/public/reporting_api_client.ts +++ b/packages/kbn-reporting/public/reporting_api_client.ts @@ -215,18 +215,6 @@ export class ReportingAPIClient implements IReportingAPI { } } - /** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ - public async createImmediateReport(baseParams: BaseParams) { - const { objectType: _objectType, ...params } = baseParams; // objectType is not needed for immediate download api - return this.http.post(INTERNAL_ROUTES.DOWNLOAD_CSV, { - asResponse: true, - body: JSON.stringify(params), - }); - } - /** * Adds the browserTimezone and kibana version to report job params */ diff --git a/packages/kbn-reporting/public/types.ts b/packages/kbn-reporting/public/types.ts index 2579ee42fe0dd..e01a1b55b0a1f 100644 --- a/packages/kbn-reporting/public/types.ts +++ b/packages/kbn-reporting/public/types.ts @@ -25,7 +25,6 @@ export interface ReportingPublicPluginStartDependencies { export interface ClientConfigType { csv: { - enablePanelActionDownload: boolean; scroll: { duration: string; size: number; diff --git a/packages/kbn-reporting/server/config_schema.ts b/packages/kbn-reporting/server/config_schema.ts index 7d831f95fc006..a4117341d27ca 100644 --- a/packages/kbn-reporting/server/config_schema.ts +++ b/packages/kbn-reporting/server/config_schema.ts @@ -60,7 +60,7 @@ const CaptureSchema = schema.object({ const CsvSchema = schema.object({ checkForFormulas: schema.boolean({ defaultValue: true }), escapeFormulaValues: schema.boolean({ defaultValue: false }), - enablePanelActionDownload: schema.boolean({ defaultValue: false }), + enablePanelActionDownload: schema.boolean({ defaultValue: false }), // unused as of 9.0 maxSizeBytes: schema.oneOf([schema.number(), schema.byteSize()], { defaultValue: ByteSizeValue.parse('250mb'), }), diff --git a/packages/kbn-reporting/server/export_type.ts b/packages/kbn-reporting/server/export_type.ts index 8ccf69f5073da..2ddd69b826b60 100644 --- a/packages/kbn-reporting/server/export_type.ts +++ b/packages/kbn-reporting/server/export_type.ts @@ -100,7 +100,6 @@ export abstract class ExportType< } } - // needed to be protected vs private for the csv search source immediate export type protected getUiSettingsServiceFactory(savedObjectsClient: SavedObjectsClientContract) { const { uiSettings: uiSettingsService } = this.startDeps; const scopedUiSettingsService = uiSettingsService.asScopedToClient(savedObjectsClient); diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index f530e870665f8..a9d8f26a3c68a 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -99,7 +99,7 @@ align-items: start; .euiDataGridHeaderCell__draggableIcon { - padding-block: $euiSizeXS / 2; // to align with a token height + padding-block: calc($euiSizeXS / 2); // to align with a token height } .euiDataGridHeaderCell__button { diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml index 287739cc03fea..d1d76367b4ec3 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml @@ -42,39 +42,47 @@ resources: value: 0ce56bde1853fed3e53282505bac65707385275a27816c29712ab04c187aa249797c82c58759b2b36c210d4e2683eda92359d739a8045cb8385c2c34d37cc9e1 # List of project maintainers maintainers: + # AppEx Operations Members + - email: 'brad.white@elastic.co' + name: 'Brad White' + username: 'brad.white' + cht_member: false - email: 'jon@elastic.co' name: 'Jonathan Budzenski' username: 'jbudz' cht_member: false - - email: 'brad.white@elastic.co' - name: 'Brad White' - username: 'brad.white' + # AppEx Platform Security Members + - email: 'aleh.zasypkin@elastic.co' + name: 'Aleh Zasypkin' + username: 'azasypkin' + cht_member: false + - email: 'larry.gregory@elastic.co' + name: 'Larry Gregory' + username: 'legrego' + cht_member: false + # InfoSec Members + - email: 'abby.zumstein@elastic.co' + name: 'Abby Zumstein' + username: 'azumstein' + cht_member: false + - email: 'arsalan.khan@elastic.co' + name: 'Arsalan Khan' + username: 'khanarsalan' + cht_member: false + - email: 'iaroslava.zhomir@elastic.co' + name: 'Slava Zhomir' + username: 'slava-elastic' + cht_member: false + - email: 'ryan.kam@elastic.co' + name: 'Ryan Kam' + username: 'ryankam' + cht_member: false + - email: 'saumya.shree@elastic.co' + name: 'Saumya Shree' + username: 'shreesaumya' cht_member: false + # CHT Members - email: 'klepal_alexander@bah.com' name: 'Alexander Klepal' username: 'alexander.klepal' - cht_member: true - - cht_member: false - email: larry.gregory@elastic.co - name: Larry Gregory - username: legrego - - cht_member: false - email: aleh.zasypkin@elastic.co - name: Aleh Zasypkin - username: azasypkin - - cht_member: false - email: kurt.greiner@elastic.co - name: Kurt Greiner - username: kc13greiner - - cht_member: false - email: jeramy.soucy@elastic.co - name: Jeramy Soucy - username: jeramysoucy - - cht_member: false - email: sid.mantri@elastic.co - name: Sid Mantri - username: sidmantri - - cht_member: false - email: elena.shostak@elastic.co - name: Elena Shostak - username: elena.shostak + cht_member: true \ No newline at end of file diff --git a/x-pack/examples/testing_embedded_lens/public/controls.tsx b/x-pack/examples/testing_embedded_lens/public/controls.tsx index 6cfcd414ed66d..6f7658e981e4d 100644 --- a/x-pack/examples/testing_embedded_lens/public/controls.tsx +++ b/x-pack/examples/testing_embedded_lens/public/controls.tsx @@ -150,7 +150,7 @@ export function OverrideSwitch({ } helpText={helpText} - display="columnCompressedSwitch" + display="columnCompressed" hasChildLabel={false} > ) : null} {isPieChart(currentAttributes) ? ( - + ) : null} {isHeatmapChart(currentAttributes) ? ( - + ) : null} {isGaugeChart(currentAttributes) ? ( - + } - display="columnCompressedSwitch" + display="columnCompressed" helpText="Pass a consumer defined action to show in the panel context menu." > = React.memo( = ({ onValueChange, argV /> - + { +// FLAKY: https://github.com/elastic/kibana/issues/195672 +describe.skip('DeleteAttachmentConfirmationModal', () => { let appMock: AppMockRenderer; const props = { title: 'My title', diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts index 40194494854fc..853da3b3f8cbd 100644 --- a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts +++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts @@ -123,6 +123,13 @@ export const UsageMetricsAutoOpsResponseSchema = { ), }), }; -export type UsageMetricsAutoOpsResponseSchemaBody = TypeOf< +export type UsageMetricsAutoOpsResponseMetricSeries = TypeOf< typeof UsageMetricsAutoOpsResponseSchema.body ->; +>['metrics'][MetricTypes][number]; + +export type UsageMetricsAutoOpsResponseSchemaBody = Omit< + TypeOf, + 'metrics' +> & { + metrics: Partial>; +}; diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts index 93b31033fc4fb..a714259e1e11c 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts @@ -6,7 +6,6 @@ */ import { RequestHandler } from '@kbn/core/server'; -import { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types'; import { MetricTypes, UsageMetricsAutoOpsResponseSchemaBody, @@ -44,12 +43,22 @@ export const getUsageMetricsHandler = ( new CustomHttpRequestError('[request body.dataStreams]: no data streams selected', 400) ); } + let dataStreamsResponse; - const { data_streams: dataStreamsResponse }: IndicesGetDataStreamResponse = - await esClient.indices.getDataStream({ + try { + // Attempt to fetch data streams + const { data_streams: dataStreams } = await esClient.indices.getDataStream({ name: requestDsNames, expand_wildcards: 'all', }); + dataStreamsResponse = dataStreams; + } catch (error) { + return errorHandler( + logger, + response, + new CustomHttpRequestError('Failed to retrieve data streams', 400) + ); + } const metrics = await dataUsageService.getMetrics({ from, to, @@ -69,7 +78,7 @@ export const getUsageMetricsHandler = ( }; }; -function transformMetricsData( +export function transformMetricsData( data: UsageMetricsAutoOpsResponseSchemaBody ): UsageMetricsResponseSchemaBody { return { diff --git a/x-pack/plugins/data_usage/server/services/autoops_api.ts b/x-pack/plugins/data_usage/server/services/autoops_api.ts index e5ffe24c6167a..6a9de27f996f1 100644 --- a/x-pack/plugins/data_usage/server/services/autoops_api.ts +++ b/x-pack/plugins/data_usage/server/services/autoops_api.ts @@ -13,6 +13,7 @@ import type { AxiosError, AxiosRequestConfig } from 'axios'; import axios from 'axios'; import { LogMeta } from '@kbn/core/server'; import { + UsageMetricsAutoOpsResponseSchema, UsageMetricsAutoOpsResponseSchemaBody, UsageMetricsRequestBody, } from '../../common/rest_types'; @@ -134,8 +135,10 @@ export class AutoOpsAPIService { } ); + const validatedResponse = UsageMetricsAutoOpsResponseSchema.body().validate(response.data); + logger.debug(`[AutoOps API] Successfully created an autoops agent ${response}`); - return response; + return validatedResponse; } private createTlsConfig(autoopsConfig: AutoOpsConfig | undefined) { diff --git a/x-pack/plugins/data_usage/server/services/index.ts b/x-pack/plugins/data_usage/server/services/index.ts index 9ccd08861a26c..3752553e50e9f 100644 --- a/x-pack/plugins/data_usage/server/services/index.ts +++ b/x-pack/plugins/data_usage/server/services/index.ts @@ -41,7 +41,7 @@ export class DataUsageService { metricTypes, dataStreams, }); - return response.data; + return response; } catch (error) { if (error instanceof ValidationError) { throw new AutoOpsError(error.message); diff --git a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_view.tsx b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_view.tsx index 170c96d1e0682..06f600928e2a2 100644 --- a/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/data_drift/data_drift_view.tsx @@ -216,7 +216,7 @@ export const DataDriftView = ({ runAnalysisDisabled={!dataView || requiresWindowParameters} > - + { onIndexNameValidationEnd={this.props.onIndexNameValidationEnd} /> - + )} - {layerDatasource?.LayerSettingsComponent && visualizationLayerSettings.data ? ( - - ) : null} {activeVisualization?.LayerSettingsComponent && visualizationLayerSettings.data ? ( return ( <> ) /> = ({ label={i18n.translate('xpack.lens.xyChart.missingValuesStyle', { defaultMessage: 'Show as dotted line', })} - display="columnCompressedSwitch" + display="columnCompressed" > {!!styleOptions.useCustomColorRamp ? null : ( - + (props: Props - + <> (props: Props - + <> - + { } return ( - + + + + + + { return !isVectorLayer(props.layer) ? null : ( - + { _renderSwitches() { return mapEmbeddablesSingleton.getMapPanels().map((mapPanel) => { return ( - + { const hasErrors = synchronizedPanels.length === 1 && mapPanel.getIsMovementSynchronized(); return ( { diff --git a/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js b/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js index 7a1ee5c582d7d..ca024e9b42286 100644 --- a/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js @@ -63,8 +63,8 @@ console.log(JSON.stringify(pipeline, null, 2)); function getBuildJob() { return { command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', - agents: { queue: 'c2-8' }, + label: 'Build Kibana Distribution', + agents: { queue: 'n2-8' }, key: BUILD_UUID, if: `build.env('${KIBANA_BUILD_ID}') == null || build.env('${KIBANA_BUILD_ID}') == ''`, }; diff --git a/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js b/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js index 7a1ee5c582d7d..ca024e9b42286 100644 --- a/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js @@ -63,8 +63,8 @@ console.log(JSON.stringify(pipeline, null, 2)); function getBuildJob() { return { command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', - agents: { queue: 'c2-8' }, + label: 'Build Kibana Distribution', + agents: { queue: 'n2-8' }, key: BUILD_UUID, if: `build.env('${KIBANA_BUILD_ID}') == null || build.env('${KIBANA_BUILD_ID}') == ''`, }; diff --git a/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js b/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js index 6a01e8c52d002..b9a1782820aeb 100644 --- a/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js @@ -64,8 +64,8 @@ console.log(JSON.stringify(pipeline, null, 2)); function getBuildJob() { return { command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', - agents: { queue: 'c2-8' }, + label: 'Build Kibana Distribution', + agents: { queue: 'n2-8' }, key: BUILD_UUID, if: `build.env('${KIBANA_BUILD_ID}') == null || build.env('${KIBANA_BUILD_ID}') == ''`, }; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts index 4bafc3d173156..536935a4b3894 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts @@ -18,7 +18,8 @@ import { typeInOsqueryFieldInput, } from '../../tasks/live_query'; -describe('EcsMapping', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/192128 +describe.skip('EcsMapping', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { beforeEach(() => { initializeDataViews(); }); diff --git a/x-pack/plugins/reporting/README.md b/x-pack/plugins/reporting/README.md index f9d6fe4e3db40..7e4d1beefde19 100644 --- a/x-pack/plugins/reporting/README.md +++ b/x-pack/plugins/reporting/README.md @@ -4,8 +4,6 @@ An awesome Kibana reporting plugin ## csv_searchsource. This is the endpoint used in the Discover UI. It must be replaced by csv_v2 at some point, when we have more capacity in reporting. https://github.com/elastic/kibana/issues/151190 -## csv_searchsource_immediate. -This is deprecated. This export type provides users with the means to download a CSV export of a saved search without creating a report. It is only available to users when `xpack.reporting.csv.enablePanelActionDownload` is set to `true` in kibana.yml. The default is `false`, which provides users with the means to generate a normal CSV report. ## csv_v2. This new endpoint is designed to have a more automation-friendly signature. It will replace csv_searchsource in the UI at some point, when there is more capacity in reporting. It will need a little more work to have parity: it needs to be able to export "unsaved" searches. diff --git a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx index 97c5fae9cc5aa..19337a6f46deb 100644 --- a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx +++ b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx @@ -55,7 +55,6 @@ export const mockConfig: ClientConfigType = { duration: '10m', size: 500, }, - enablePanelActionDownload: false, }, poll: { jobsRefresh: { diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index a44a51d7d9ae4..709d072a9035a 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -24,6 +24,7 @@ export const config: PluginConfigDescriptor = { }, schema: ConfigSchema, deprecations: ({ unused }) => [ + unused('csv.enablePanelActionDownload', { level: 'warning' }), // unused since 9.0 unused('queue.indexInterval', { level: 'warning' }), // unused since 8.15 unused('capture.browser.chromium.maxScreenshotDimension', { level: 'warning' }), // unused since 7.8 unused('capture.browser.type', { level: 'warning' }), diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 794cb5180ea09..283488dd5a973 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -29,11 +29,7 @@ import type { FeaturesPluginSetup } from '@kbn/features-plugin/server'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/server'; import type { ReportingServerInfo } from '@kbn/reporting-common/types'; -import { - CsvSearchSourceExportType, - CsvSearchSourceImmediateExportType, - CsvV2ExportType, -} from '@kbn/reporting-export-types-csv'; +import { CsvSearchSourceExportType, CsvV2ExportType } from '@kbn/reporting-export-types-csv'; import { PdfExportType, PdfV1ExportType } from '@kbn/reporting-export-types-pdf'; import { PngExportType } from '@kbn/reporting-export-types-png'; import type { ReportingConfigType } from '@kbn/reporting-server'; @@ -383,22 +379,4 @@ export class ReportingCore { const ReportingEventLogger = reportingEventLoggerFactory(this.logger); return new ReportingEventLogger(report, task); } - - /** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ - public async getCsvSearchSourceImmediate() { - const startDeps = await this.getPluginStartDeps(); - - const csvImmediateExport = new CsvSearchSourceImmediateExportType( - this.core, - this.config, - this.logger, - this.context - ); - csvImmediateExport.setup(this.getPluginSetupDeps()); - csvImmediateExport.start({ ...startDeps }); - return csvImmediateExport; - } } diff --git a/x-pack/plugins/reporting/server/lib/event_logger/types.ts b/x-pack/plugins/reporting/server/lib/event_logger/types.ts index 8a6725eb33a4d..4b792446ff4c7 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/types.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/types.ts @@ -20,7 +20,7 @@ export interface ReportingAction extends LogMeta { kibana: { reporting: { actionType: A; - id?: string; // "immediate download" exports have no ID + id: string; jobType: string; byteSize?: number; } & TaskRunMetrics; diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index 750cd0d5cd6fb..3473f660f647f 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -9,7 +9,6 @@ import type { Logger } from '@kbn/core/server'; import { ReportingCore } from '..'; import { registerDeprecationsRoutes } from './internal/deprecations/deprecations'; import { registerDiagnosticRoutes } from './internal/diagnostic'; -import { registerGenerateCsvFromSavedObjectImmediate } from './internal/generate/csv_searchsource_immediate'; import { registerGenerationRoutesInternal } from './internal/generate/generate_from_jobparams'; import { registerJobInfoRoutesInternal } from './internal/management/jobs'; import { registerGenerationRoutesPublic } from './public/generate_from_jobparams'; @@ -22,10 +21,4 @@ export function registerRoutes(reporting: ReportingCore, logger: Logger) { registerJobInfoRoutesInternal(reporting); registerGenerationRoutesPublic(reporting, logger); registerJobInfoRoutesPublic(reporting); - - // (deprecated) allow users to download CSV without generating a report - const config = reporting.getConfig(); - if (config.csv.enablePanelActionDownload) { - registerGenerateCsvFromSavedObjectImmediate(reporting, logger); - } } diff --git a/x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts b/x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts deleted file mode 100644 index cc9ad946d84ef..0000000000000 --- a/x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts +++ /dev/null @@ -1,137 +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 Boom from '@hapi/boom'; -import moment from 'moment'; - -import { schema } from '@kbn/config-schema'; -import type { KibanaRequest, Logger } from '@kbn/core/server'; -import { INTERNAL_ROUTES } from '@kbn/reporting-common'; -import { - CSV_SEARCHSOURCE_IMMEDIATE_TYPE, - JobParamsDownloadCSV, -} from '@kbn/reporting-export-types-csv-common'; -import type { ReportingCore } from '../../..'; -import { PassThroughStream } from '../../../lib'; -import { authorizedUserPreRouting, getCounters } from '../../common'; - -const path = INTERNAL_ROUTES.DOWNLOAD_CSV; - -export type CsvFromSavedObjectRequest = KibanaRequest; - -/* - * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: - * - saved object type and ID - * - time range and time zone - * - application state: - * - filters - * - query bar - * - local (transient) changes the user made to the saved object - */ -export function registerGenerateCsvFromSavedObjectImmediate( - reporting: ReportingCore, - parentLogger: Logger -) { - const setupDeps = reporting.getPluginSetupDeps(); - const { router } = setupDeps; - - const useKibanaAccessControl = reporting.getDeprecatedAllowedRoles() === false; // true if deprecated config is turned off - const kibanaAccessControlTags = useKibanaAccessControl ? ['access:downloadCsv'] : []; - - // This API calls run the SearchSourceImmediate export type's runTaskFn directly - router.post( - { - path, - validate: { - body: schema.object({ - columns: schema.maybe(schema.arrayOf(schema.string())), - searchSource: schema.object({}, { unknowns: 'allow' }), - browserTimezone: schema.string({ - defaultValue: 'UTC', - validate: (value) => - moment.tz.zone(value) ? undefined : `Invalid timezone "${typeof value}".`, - }), - title: schema.string(), - version: schema.maybe(schema.string()), - }), - }, - options: { tags: kibanaAccessControlTags, access: 'internal' }, - }, - authorizedUserPreRouting( - reporting, - async (user, context, req: CsvFromSavedObjectRequest, res) => { - const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); - - const logger = parentLogger.get(CSV_SEARCHSOURCE_IMMEDIATE_TYPE); - const csvSearchSourceImmediateExport = await reporting.getCsvSearchSourceImmediate(); - - const stream = new PassThroughStream(); - const eventLog = reporting.getEventLogger({ - jobtype: CSV_SEARCHSOURCE_IMMEDIATE_TYPE, - created_by: user && user.username, - payload: { browserTimezone: req.body.browserTimezone }, - }); - const logError = (error: Error) => { - logger.error(error); - eventLog.logError(error); - }; - - try { - eventLog.logExecutionStart(); - - const taskPromise = csvSearchSourceImmediateExport - .runTask(null, req.body, context, stream, req) - .then((output) => { - logger.info(`Job output size: ${stream.bytesWritten} bytes.`); - - if (!stream.bytesWritten) { - logger.warn('CSV Job Execution created empty content result'); - } - - eventLog.logExecutionComplete({ - ...(output.metrics ?? {}), - byteSize: stream.bytesWritten, - }); - }) - .finally(() => stream.end()); - - await Promise.race([stream.firstBytePromise, taskPromise]); - - taskPromise.catch(logError); - - counters.usageCounter(); - - return res.ok({ - body: stream, - headers: { - 'content-type': 'text/csv;charset=utf-8', - 'accept-ranges': 'none', - }, - }); - } catch (error) { - logError(error); - - if (error instanceof Boom.Boom) { - const statusCode = error.output.statusCode; - counters.errorCounter(undefined, statusCode); - - return res.customError({ - statusCode, - body: error.output.payload.message, - }); - } - - counters.errorCounter(undefined, 500); - - return res.customError({ - statusCode: 500, - }); - } - } - ) - ); -} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx index 56a559a91794a..e4b00196f4768 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx @@ -10,7 +10,7 @@ import { EuiBadge } from '@elastic/eui'; import * as i18n from './translations'; import { isCustomizedPrebuiltRule } from '../../../../../common/api/detection_engine'; import type { RuleResponse } from '../../../../../common/api/detection_engine'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../hooks/use_is_prebuilt_rules_customization_enabled'; interface CustomizedPrebuiltRuleBadgeProps { rule: RuleResponse | null; @@ -19,9 +19,7 @@ interface CustomizedPrebuiltRuleBadgeProps { export const CustomizedPrebuiltRuleBadge: React.FC = ({ rule, }) => { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); if (!isPrebuiltRulesCustomizationEnabled) { return null; @@ -31,5 +29,5 @@ export const CustomizedPrebuiltRuleBadge: React.FC{i18n.CUSTOMIZED_PREBUILT_RULE_LABEL}; + return {i18n.MODIFIED_PREBUILT_RULE_LABEL}; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 89c22a285e327..e7f36e2011f3c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -350,10 +350,10 @@ export const MAX_SIGNALS_FIELD_LABEL = i18n.translate( } ); -export const CUSTOMIZED_PREBUILT_RULE_LABEL = i18n.translate( +export const MODIFIED_PREBUILT_RULE_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.customizedPrebuiltRuleLabel', { - defaultMessage: 'Customized Elastic rule', + defaultMessage: 'Modified Elastic rule', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx index b5ca86c6f1f57..5450cf9950d59 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx @@ -7,7 +7,7 @@ import { useKibana } from '../../../common/lib/kibana/kibana_react'; import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from './use_is_prebuilt_rules_customization_enabled'; /** * Gets the default index pattern for cases when rule has neither index patterns or data view. @@ -15,9 +15,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_exper */ export function useDefaultIndexPattern(): string[] { const { services } = useKibana(); - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); return isPrebuiltRulesCustomizationEnabled ? services.settings.client.get(DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx new file mode 100644 index 0000000000000..d25925860c175 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; + +export const useIsPrebuiltRulesCustomizationEnabled = () => { + return useIsExperimentalFeatureEnabled('prebuiltRulesCustomizationEnabled'); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index e12442c97aa4c..59ac52d592bcd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -99,6 +99,7 @@ export interface FilterOptions { excludeRuleTypes?: Type[]; enabled?: boolean; // undefined is to display all the rules ruleExecutionStatus?: RuleExecutionStatus; // undefined means "all" + ruleSource?: RuleCustomizationEnum[]; // undefined is to display all the rules } export interface FetchRulesResponse { @@ -202,3 +203,8 @@ export interface FindRulesReferencedByExceptionsProps { lists: FindRulesReferencedByExceptionsListProp[]; signal?: AbortSignal; } + +export enum RuleCustomizationEnum { + customized = 'CUSTOMIZED', + not_customized = 'NOT_CUSTOMIZED', +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index aad1e053e15b6..6ec9ffdd02e67 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -8,8 +8,8 @@ import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RulesUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { RuleUpgradeConflictsResolverTab } from '../../../../rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab'; import { PerFieldRuleDiffTab } from '../../../../rule_management/components/rule_details/per_field_rule_diff_tab'; import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages'; @@ -111,13 +111,12 @@ interface UpgradePrebuiltRulesTableContextProviderProps { export const UpgradePrebuiltRulesTableContextProvider = ({ children, }: UpgradePrebuiltRulesTableContextProviderProps) => { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const [loadingRules, setLoadingRules] = useState([]); const [filterOptions, setFilterOptions] = useState({ filter: '', tags: [], + ruleSource: [], }); const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx index 215a810bf3aa2..900d81d0b0037 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx @@ -9,10 +9,13 @@ import { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; +import type { RuleCustomizationEnum } from '../../../../rule_management/logic'; import * as i18n from './translations'; import { TagsFilterPopover } from '../rules_table_filters/tags_filter_popover'; import { RuleSearchField } from '../rules_table_filters/rule_search_field'; import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; +import { RuleCustomizationFilterPopover } from './upgrade_rule_customization_filter_popover'; const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeM}; @@ -28,7 +31,9 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { actions: { setFilterOptions }, } = useUpgradePrebuiltRulesTableContext(); - const { tags: selectedTags } = filterOptions; + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); + + const { tags: selectedTags, ruleSource: selectedRuleSource = [] } = filterOptions; const handleOnSearch = useCallback( (filterString: string) => { @@ -52,22 +57,45 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { [selectedTags, setFilterOptions] ); + const handleSelectedRuleSource = useCallback( + (newRuleSource: RuleCustomizationEnum[]) => { + if (!isEqual(newRuleSource, selectedRuleSource)) { + setFilterOptions((filters) => ({ + ...filters, + ruleSource: newRuleSource, + })); + } + }, + [selectedRuleSource, setFilterOptions] + ); + return ( - + - - - + + {isPrebuiltRulesCustomizationEnabled && ( + + + + )} + + + + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx new file mode 100644 index 0000000000000..234943e333272 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx @@ -0,0 +1,92 @@ +/* + * 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, { useState, useMemo } from 'react'; +import type { EuiSelectableOption } from '@elastic/eui'; +import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui'; +import { RuleCustomizationEnum } from '../../../../rule_management/logic'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; +import { toggleSelectedGroup } from '../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; + +interface RuleCustomizationFilterPopoverProps { + selectedRuleSource: RuleCustomizationEnum[]; + onSelectedRuleSourceChanged: (newRuleSource: RuleCustomizationEnum[]) => void; +} + +const RULE_CUSTOMIZATION_POPOVER_WIDTH = 200; + +const RuleCustomizationFilterPopoverComponent = ({ + selectedRuleSource, + onSelectedRuleSourceChanged, +}: RuleCustomizationFilterPopoverProps) => { + const [isRuleCustomizationPopoverOpen, setIsRuleCustomizationPopoverOpen] = useState(false); + + const selectableOptions: EuiSelectableOption[] = useMemo( + () => [ + { + label: i18n.MODIFIED_LABEL, + key: RuleCustomizationEnum.customized, + checked: selectedRuleSource.includes(RuleCustomizationEnum.customized) ? 'on' : undefined, + }, + { + label: i18n.UNMODIFIED_LABEL, + key: RuleCustomizationEnum.not_customized, + checked: selectedRuleSource.includes(RuleCustomizationEnum.not_customized) + ? 'on' + : undefined, + }, + ], + [selectedRuleSource] + ); + + const handleSelectableOptionsChange = ( + newOptions: EuiSelectableOption[], + _: unknown, + changedOption: EuiSelectableOption + ) => { + toggleSelectedGroup( + changedOption.key ?? '', + selectedRuleSource, + onSelectedRuleSourceChanged as (args: string[]) => void + ); + }; + + const triggerButton = ( + setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} + numFilters={selectableOptions.length} + isSelected={isRuleCustomizationPopoverOpen} + hasActiveFilters={selectedRuleSource.length > 0} + numActiveFilters={selectedRuleSource.length} + data-test-subj="rule-customization-filter-popover-button" + > + {i18n.RULE_SOURCE} + + ); + + return ( + setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} + panelPaddingSize="none" + repositionOnScroll + panelProps={{ + 'data-test-subj': 'rule-customization-filter-popover', + }} + > + + {(list) =>
{list}
} +
+
+ ); +}; + +export const RuleCustomizationFilterPopover = React.memo(RuleCustomizationFilterPopoverComponent); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts index 342a1e6e8768e..b5a0e123d7510 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts @@ -7,9 +7,12 @@ import { useMemo } from 'react'; import type { RuleUpgradeInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { FilterOptions } from '../../../../rule_management/logic/types'; +import { RuleCustomizationEnum, type FilterOptions } from '../../../../rule_management/logic/types'; -export type UpgradePrebuiltRulesTableFilterOptions = Pick; +export type UpgradePrebuiltRulesTableFilterOptions = Pick< + FilterOptions, + 'filter' | 'tags' | 'ruleSource' +>; export const useFilterPrebuiltRulesToUpgrade = ({ rules, @@ -19,7 +22,7 @@ export const useFilterPrebuiltRulesToUpgrade = ({ filterOptions: UpgradePrebuiltRulesTableFilterOptions; }) => { const filteredRules = useMemo(() => { - const { filter, tags } = filterOptions; + const { filter, tags, ruleSource } = filterOptions; return rules.filter((ruleInfo) => { if (filter && !ruleInfo.current_rule.name.toLowerCase().includes(filter.toLowerCase())) { return false; @@ -29,6 +32,25 @@ export const useFilterPrebuiltRulesToUpgrade = ({ return tags.every((tag) => ruleInfo.current_rule.tags.includes(tag)); } + if (ruleSource && ruleSource.length > 0) { + if ( + ruleSource.includes(RuleCustomizationEnum.customized) && + ruleSource.includes(RuleCustomizationEnum.not_customized) + ) { + return true; + } else if ( + ruleSource.includes(RuleCustomizationEnum.customized) && + ruleInfo.current_rule.rule_source.type === 'external' + ) { + return ruleInfo.current_rule.rule_source.is_customized; + } else if ( + ruleSource.includes(RuleCustomizationEnum.not_customized) && + ruleInfo.current_rule.rule_source.type === 'external' + ) { + return ruleInfo.current_rule.rule_source.is_customized === false; + } + } + return true; }); }, [filterOptions, rules]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index 29c5b2b201fe6..8c97a4ef52e2b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -6,7 +6,7 @@ */ import { useCallback, useMemo, useState } from 'react'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RulesUpgradeState, FieldsUpgradeState, @@ -33,9 +33,7 @@ interface UseRulesUpgradeStateResult { export function usePrebuiltRulesUpgradeState( ruleUpgradeInfos: RuleUpgradeInfoForReview[] ): UseRulesUpgradeStateResult { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const [rulesResolvedConflicts, setRulesResolvedConflicts] = useState({}); const setRuleFieldResolvedValue = useCallback( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index 09009c98c2858..579f571f80e79 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -6,7 +6,14 @@ */ import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiBadge, EuiButtonEmpty, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { + EuiBadge, + EuiButtonEmpty, + EuiLink, + EuiLoadingSpinner, + EuiText, + EuiToolTip, +} from '@elastic/eui'; import React, { useMemo } from 'react'; import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade/rule_upgrade_state'; import { RulesTableEmptyColumnName } from '../rules_table_empty_column_name'; @@ -104,6 +111,35 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; +const MODIFIED_COLUMN: TableColumn = { + field: 'current_rule.rule_source', + name: , + align: 'center', + render: (ruleSource: Rule['rule_source']) => { + if ( + ruleSource == null || + ruleSource.type === 'internal' || + (ruleSource.type === 'external' && ruleSource.is_customized === false) + ) { + return null; + } + + return ( + + + {i18n.MODIFIED_LABEL} + + + ); + }, + width: '90px', + truncateText: true, +}; + const createUpgradeButtonColumn = ( upgradeRules: UpgradePrebuiltRulesTableActions['upgradeRules'], loadingRules: RuleSignatureId[], @@ -154,9 +190,15 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => { } = useUpgradePrebuiltRulesTableContext(); const isDisabled = isRefetching || isUpgradingSecurityPackages; + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ RULE_NAME_COLUMN, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index c38fec638e478..ae24b2bb482d3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -46,6 +46,7 @@ import { useRulesTableActions } from './use_rules_table_actions'; import { MlRuleWarningPopover } from '../ml_rule_warning_popover/ml_rule_warning_popover'; import { getMachineLearningJobId } from '../../../../detections/pages/detection_engine/rules/helpers'; import type { TimeRange } from '../../../rule_gaps/types'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -233,6 +234,35 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; +const MODIFIED_COLUMN: TableColumn = { + field: 'rule_source', + name: , + align: 'center', + render: (ruleSource: Rule['rule_source']) => { + if ( + ruleSource == null || + ruleSource.type === 'internal' || + (ruleSource.type === 'external' && ruleSource.is_customized === false) + ) { + return null; + } + + return ( + + + {i18n.MODIFIED_LABEL} + + + ); + }, + width: '90px', + truncateText: true, +}; + const useActionsColumn = ({ showExceptionsDuplicateConfirmation, showManualRuleRunConfirmation, @@ -265,6 +295,7 @@ export const useRulesColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -279,9 +310,15 @@ export const useRulesColumns = ({ }); const snoozeColumn = useRuleSnoozeColumn(); + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ ruleNameColumn, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { @@ -352,13 +389,14 @@ export const useRulesColumns = ({ ...(hasCRUDPermissions ? [actionsColumn] : []), ], [ - actionsColumn, - enabledColumn, + ruleNameColumn, + isPrebuiltRulesCustomizationEnabled, + showRelatedIntegrations, executionStatusColumn, snoozeColumn, + enabledColumn, hasCRUDPermissions, - ruleNameColumn, - showRelatedIntegrations, + actionsColumn, ] ); }; @@ -380,6 +418,7 @@ export const useMonitoringColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -393,12 +432,18 @@ export const useMonitoringColumns = ({ mlJobs, }); + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ { ...ruleNameColumn, width: '28%', }, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { @@ -503,6 +548,7 @@ export const useMonitoringColumns = ({ enabledColumn, executionStatusColumn, hasCRUDPermissions, + isPrebuiltRulesCustomizationEnabled, ruleNameColumn, showRelatedIntegrations, ] diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx index 2d2875d5a8734..49b6c1d1e4e99 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx @@ -16,6 +16,7 @@ import { EuiSpacer, } from '@elastic/eui'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../../detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RelatedIntegrationArray } from '../../../../../../common/api/detection_engine/model/rule_schema'; import { IntegrationDescription } from '../integrations_description'; import { useRelatedIntegrations } from '../use_related_integrations'; @@ -54,6 +55,7 @@ const IntegrationListItem = styled('li')` const IntegrationsPopoverComponent = ({ relatedIntegrations }: IntegrationsPopoverProps) => { const [isPopoverOpen, setPopoverOpen] = useState(false); const { integrations, isLoaded } = useRelatedIntegrations(relatedIntegrations); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledIntegrations = useMemo(() => { return integrations.filter( @@ -65,10 +67,13 @@ const IntegrationsPopoverComponent = ({ relatedIntegrations }: IntegrationsPopov const numIntegrationsEnabled = enabledIntegrations.length; const badgeTitle = useMemo(() => { + if (isPrebuiltRulesCustomizationEnabled) { + return isLoaded ? `${numIntegrationsEnabled}/${numIntegrations}` : `${numIntegrations}`; + } return isLoaded ? `${numIntegrationsEnabled}/${numIntegrations} ${i18n.INTEGRATIONS_BADGE}` : `${numIntegrations} ${i18n.INTEGRATIONS_BADGE}`; - }, [isLoaded, numIntegrations, numIntegrationsEnabled]); + }, [isLoaded, isPrebuiltRulesCustomizationEnabled, numIntegrations, numIntegrationsEnabled]); return ( { - log.debug('in navigateToDashboardApp'); - await dashboard.navigateToApp(); - await retry.tryForTime(10000, async () => { - expect(await dashboard.onDashboardLandingPage()).to.be(true); - }); - }; - - const getCsvReportData = async () => { - await toasts.dismissAll(); - const url = await reporting.getReportURL(60000); - const res = await reporting.getResponse(url ?? ''); - - expect(res.status).to.equal(200); - expect(res.get('content-type')).to.equal('text/csv; charset=utf-8'); - return res.text; - }; - - const clickDownloadCsv = async (wrapper?: WebElementWrapper) => { - log.debug('click "Generate CSV"'); - await dashboardPanelActions.clickPanelAction( - 'embeddablePanelAction-generateCsvReport', - wrapper - ); - await testSubjects.existOrFail('csvReportStarted'); // validate toast panel - }; - - const clickDownloadCsvByTitle = async (title?: string) => { - log.debug(`click "Generate CSV" on "${title}"`); - await dashboardPanelActions.clickPanelActionByTitle( - 'embeddablePanelAction-generateCsvReport', - title - ); - await testSubjects.existOrFail('csvReportStarted'); // validate toast panel - }; - - const createPartialCsv = (csvFile: unknown) => { - const partialCsvFile = (csvFile as string).split('\n').slice(0, 4); - return partialCsvFile.join('\n'); - }; - - /* - * Tests - */ - describe('Dashboard Generate CSV', () => { - describe('Default Saved Search Data', () => { - before(async () => { - await esArchiver.emptyKibanaIndex(); - await reportingService.initEcommerce(); - await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); - }); - - beforeEach(async () => { - await navigateToDashboardApp(); - }); - - after(async () => { - await reportingService.teardownEcommerce(); - }); - - it('Generate CSV export of a saved search panel', async function () { - await dashboard.loadSavedDashboard('Ecom Dashboard - 3 Day Period'); - await clickDownloadCsvByTitle('EcommerceData'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(76137); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - - it('Downloads a filtered CSV export of a saved search panel', async function () { - await dashboard.loadSavedDashboard('Ecom Dashboard - 3 Day Period'); - - // add a filter - await filterBar.addFilter({ field: 'category', operation: 'is', value: `Men's Shoes` }); - await clickDownloadCsvByTitle('EcommerceData'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(17106); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - - it('Downloads a saved search panel with a custom time range that does not intersect with dashboard time range', async function () { - await dashboard.loadSavedDashboard('Ecom Dashboard - 3 Day Period - custom time range'); - await clickDownloadCsvByTitle('EcommerceData'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(23277); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - - it('Gets the correct filename if panel titles are hidden', async () => { - await dashboard.loadSavedDashboard('Ecom Dashboard Hidden Panel Titles'); - const savedSearchPanel = await dashboardPanelActions.getPanelWrapperById( - '94eab06f-60ac-4a85-b771-3a8ed475c9bb' - ); // panel title is hidden - - await clickDownloadCsv(savedSearchPanel); - await testSubjects.existOrFail('csvReportStarted'); - - const csvFile = await getCsvReportData(); - expect(csvFile).to.not.be(null); - }); - }); - - describe('Filtered Saved Search', () => { - const TEST_SEARCH_TITLE = 'Customer Betty'; - const TEST_DASHBOARD_TITLE = 'Filtered Search Data'; - const from = 'Jun 20, 2019 @ 23:56:51.374'; - const to = 'Jun 25, 2019 @ 16:18:51.821'; - - before(async () => { - await esArchiver.emptyKibanaIndex(); - await reportingService.initEcommerce(); - await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); - }); - - beforeEach(async () => { - await navigateToDashboardApp(); - log.info(`Creating empty dashboard`); - await dashboard.clickNewDashboard(); - await timePicker.setAbsoluteRange(from, to); - log.info(`Adding "${TEST_SEARCH_TITLE}" to dashboard`); - await dashboardAddPanel.addSavedSearch(TEST_SEARCH_TITLE); - await dashboard.saveDashboard(TEST_DASHBOARD_TITLE); - }); - - after(async () => { - await reportingService.teardownEcommerce(); - await common.unsetTime(); - }); - - it('Downloads filtered Discover saved search report', async () => { - await clickDownloadCsvByTitle(TEST_SEARCH_TITLE); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(2446); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - }); - - describe('Field Formatters and Scripted Fields', () => { - const dashboardWithScriptedFieldsSearch = 'names dashboard'; - - before(async () => { - await esArchiver.emptyKibanaIndex(); - await reportingService.initLogs(); - await esArchiver.load('x-pack/test/functional/es_archives/reporting/hugedata'); - await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); - }); - - beforeEach(async () => { - await navigateToDashboardApp(); - await dashboard.loadSavedDashboard(dashboardWithScriptedFieldsSearch); - await timePicker.setAbsoluteRange( - 'Nov 26, 1981 @ 21:54:15.526', - 'Mar 5, 1982 @ 18:17:44.821' - ); - - await common.sleep(1000); - await filterBar.addFilter({ field: 'name.keyword', operation: 'is', value: 'Fethany' }); - await common.sleep(1000); - }); - - after(async () => { - await reportingService.teardownLogs(); - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/hugedata'); - }); - - it('Generate CSV export of a saved search panel', async () => { - await clickDownloadCsvByTitle('namessearch'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(166); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts index ef9821a544b3a..9dfbd61473302 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts @@ -10,6 +10,5 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Reporting', function () { loadTestFile(require.resolve('./screenshots')); - loadTestFile(require.resolve('./download_csv')); }); } diff --git a/x-pack/test/functional/apps/reporting/README.md b/x-pack/test/functional/apps/reporting/README.md index 5d47804fef284..ed2402e9d9d58 100644 --- a/x-pack/test/functional/apps/reporting/README.md +++ b/x-pack/test/functional/apps/reporting/README.md @@ -9,7 +9,6 @@ Functional tests on report generation are under the applications that use report - `x-pack/test/functional/apps/visualize/reporting.ts` **CSV Report testing:** - - `x-pack/test/functional/apps/dashboard/reporting/download_csv.ts` - `x-pack/test/functional/apps/discover/reporting.ts` Reporting Management app tests are in `functional/apps/reporting_management`. diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_with_prebuilt_rule_customization.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_with_prebuilt_rule_customization.cy.ts new file mode 100644 index 0000000000000..52b050f46c06e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_with_prebuilt_rule_customization.cy.ts @@ -0,0 +1,110 @@ +/* + * 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 { patchRule } from '../../../../tasks/api_calls/rules'; +import { createRuleAssetSavedObject } from '../../../../helpers/rules'; +import { + MODIFIED_RULE_BADGE, + RULES_UPDATES_TABLE, +} from '../../../../screens/alerts_detection_rules'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { + installPrebuiltRuleAssets, + createAndInstallMockedPrebuiltRules, +} from '../../../../tasks/api_calls/prebuilt_rules'; +import { resetRulesTableState } from '../../../../tasks/common'; +import { login } from '../../../../tasks/login'; +import { + assertRulesNotPresentInRuleUpdatesTable, + assertRulesPresentInRuleUpdatesTable, + clickRuleUpdatesTab, + filterPrebuiltRulesUpdateTableByRuleCustomization, +} from '../../../../tasks/prebuilt_rules'; +import { visitRulesManagementTable } from '../../../../tasks/rules_management'; + +describe( + 'Detection rules, Prebuilt Rules Installation and Update workflow - With Rule Customization', + { + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'prebuiltRulesCustomizationEnabled', + ])}`, + ], + }, + }, + }, + + () => { + describe('Upgrade of prebuilt rules with conflicts', () => { + const RULE_1_ID = 'rule_1'; + const RULE_2_ID = 'rule_2'; + const OUTDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Outdated rule 1', + rule_id: RULE_1_ID, + version: 1, + }); + const UPDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Updated rule 1', + rule_id: RULE_1_ID, + version: 2, + }); + const OUTDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Outdated rule 2', + rule_id: RULE_2_ID, + version: 1, + }); + const UPDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Updated rule 2', + rule_id: RULE_2_ID, + version: 2, + }); + const patchedName = 'A new name that creates a conflict'; + beforeEach(() => { + login(); + resetRulesTableState(); + deleteAlertsAndRules(); + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as( + 'updatePrebuiltRules' + ); + /* Create a new rule and install it */ + createAndInstallMockedPrebuiltRules([OUTDATED_RULE_1, OUTDATED_RULE_2]); + /* Modify one of the rule's name to cause a conflict */ + patchRule(OUTDATED_RULE_1['security-rule'].rule_id, { + name: patchedName, + }); + /* Create a second version of the rule, making it available for update */ + installPrebuiltRuleAssets([UPDATED_RULE_1, UPDATED_RULE_2]); + + visitRulesManagementTable(); + clickRuleUpdatesTab(); + }); + + it('should filter by customized prebuilt rules', () => { + // Filter table to show modified rules only + filterPrebuiltRulesUpdateTableByRuleCustomization('Modified'); + cy.get(MODIFIED_RULE_BADGE).should('exist'); + + // Verify only rules with customized rule sources are displayed + cy.get(RULES_UPDATES_TABLE).contains(patchedName); + assertRulesNotPresentInRuleUpdatesTable([OUTDATED_RULE_2]); + }); + + it('should filter by customized prebuilt rules', () => { + // Filter table to show unmodified rules only + filterPrebuiltRulesUpdateTableByRuleCustomization('Unmodified'); + cy.get(MODIFIED_RULE_BADGE).should('not.exist'); + + // Verify only rules with non-customized rule sources are displayed + assertRulesPresentInRuleUpdatesTable([OUTDATED_RULE_2]); + cy.get(patchedName).should('not.exist'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts index bbc7a346b252f..e2ce41dee2847 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts @@ -349,3 +349,5 @@ export const ESQL_QUERY_VALUE = '[data-test-subj="esqlQueryPropertyValue"]'; export const PER_FIELD_DIFF_WRAPPER = '[data-test-subj="ruleUpgradePerFieldDiffWrapper"]'; export const PER_FIELD_DIFF_DEFINITION_SECTION = '[data-test-subj="perFieldDiffDefinitionSection"]'; + +export const MODIFIED_RULE_BADGE = '[data-test-subj="upgradeRulesTableModifiedColumnBadge"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts b/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts new file mode 100644 index 0000000000000..4b11a4624c3e2 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON = + '[data-test-subj="rule-customization-filter-popover-button"]'; + +export const RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL = + '[data-test-subj="rule-customization-filter-popover"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts index 008fc92bd0224..c0ef625f52e35 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts @@ -39,6 +39,23 @@ export const createRule = ( ); }; +export const patchRule = ( + ruleId: string, + updateData: Partial +): Cypress.Chainable> => { + return cy.currentSpace().then((spaceId) => + rootRequest({ + method: 'PATCH', + url: spaceId ? getSpaceUrl(spaceId, DETECTION_ENGINE_RULES_URL) : DETECTION_ENGINE_RULES_URL, + body: { + rule_id: ruleId, + ...updateData, + }, + failOnStatusCode: false, + }) + ); +}; + /** * Snoozes a rule via API * diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts index b4f68dba976c3..d4148d5e632a1 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts @@ -17,6 +17,10 @@ import { TOASTER, } from '../screens/alerts_detection_rules'; import type { SAMPLE_PREBUILT_RULE } from './api_calls/prebuilt_rules'; +import { + RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON, + RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL, +} from '../screens/rule_updates'; export const clickAddElasticRulesButton = () => { cy.get(ADD_ELASTIC_RULES_BTN).click(); @@ -150,3 +154,9 @@ export const assertRulesNotPresentInRuleUpdatesTable = ( cy.get(rule['security-rule'].name).should('not.exist'); } }; + +export const filterPrebuiltRulesUpdateTableByRuleCustomization = (text: string) => { + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON).click(); + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL).contains(text).click(); + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON).click(); +}; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts new file mode 100644 index 0000000000000..cf31e1885d1d5 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Serverless Data Usage APIs', function () { + this.tags(['esGate']); + + loadTestFile(require.resolve('./tests/data_streams')); + loadTestFile(require.resolve('./tests/metrics')); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts new file mode 100644 index 0000000000000..0a9438e826ef3 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts @@ -0,0 +1,23 @@ +/* + * 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 { createServer } from '@mswjs/http-middleware'; +import { UsageMetricsAutoOpsResponseSchemaBody } from '@kbn/data-usage-plugin/common/rest_types'; +import { http, HttpResponse, StrictResponse } from 'msw'; +import { mockAutoOpsResponse } from './mock_data'; + +export const setupMockServer = () => { + const server = createServer(autoOpsHandler); + return server; +}; + +const autoOpsHandler = http.post( + '/', + async ({ request }): Promise> => { + return HttpResponse.json(mockAutoOpsResponse); + } +); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts new file mode 100644 index 0000000000000..c38cc57d2b546 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const mockAutoOpsResponse = { + metrics: { + ingest_rate: [ + { + name: 'metrics-system.cpu-default', + data: [ + [1726858530000, 13756849], + [1726862130000, 14657904], + ], + }, + { + name: 'logs-nginx.access-default', + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + storage_retained: [ + { + name: 'metrics-system.cpu-default', + data: [ + [1726858530000, 12576413], + [1726862130000, 13956423], + ], + }, + { + name: 'logs-nginx.access-default', + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + }, +}; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts new file mode 100644 index 0000000000000..d805b8ccff6fe --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; +import { DataStreamsResponseBodySchemaBody } from '@kbn/data-usage-plugin/common/rest_types'; +import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '@kbn/data-usage-plugin/common'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const svlDatastreamsHelpers = getService('svlDatastreamsHelpers'); + const roleScopedSupertest = getService('roleScopedSupertest'); + let supertestAdminWithCookieCredentials: SupertestWithRoleScope; + const testDataStreamName = 'test-data-stream'; + describe(`GET ${DATA_USAGE_DATA_STREAMS_API_ROUTE}`, function () { + // due to the plugin depending on yml config (xpack.dataUsage.enabled), we cannot test in MKI until it is on by default + this.tags(['skipMKI']); + before(async () => { + await svlDatastreamsHelpers.createDataStream(testDataStreamName); + supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'admin', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); + }); + after(async () => { + await svlDatastreamsHelpers.deleteDataStream(testDataStreamName); + }); + + it('returns created data streams', async () => { + const res = await supertestAdminWithCookieCredentials + .get(DATA_USAGE_DATA_STREAMS_API_ROUTE) + .set('elastic-api-version', '1'); + const dataStreams: DataStreamsResponseBodySchemaBody = res.body; + const foundStream = dataStreams.find((stream) => stream.name === testDataStreamName); + expect(foundStream?.name).to.be(testDataStreamName); + expect(foundStream?.storageSizeBytes).to.be(0); + expect(res.statusCode).to.be(200); + }); + it('returns system indices', async () => { + const res = await supertestAdminWithCookieCredentials + .get(DATA_USAGE_DATA_STREAMS_API_ROUTE) + .set('elastic-api-version', '1'); + const dataStreams: DataStreamsResponseBodySchemaBody = res.body; + const systemDataStreams = dataStreams.filter((stream) => stream.name.startsWith('.')); + expect(systemDataStreams.length).to.be.greaterThan(0); + expect(res.statusCode).to.be(200); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts new file mode 100644 index 0000000000000..8985757ab1cab --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts @@ -0,0 +1,110 @@ +/* + * 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 expect from '@kbn/expect'; +import http from 'http'; + +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; +import { UsageMetricsRequestBody } from '@kbn/data-usage-plugin/common/rest_types'; +import { DATA_USAGE_METRICS_API_ROUTE } from '@kbn/data-usage-plugin/common'; +import { transformMetricsData } from '@kbn/data-usage-plugin/server/routes/internal/usage_metrics_handler'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { setupMockServer } from '../mock_api'; +import { mockAutoOpsResponse } from '../mock_data'; + +export default function ({ getService }: FtrProviderContext) { + const svlDatastreamsHelpers = getService('svlDatastreamsHelpers'); + const roleScopedSupertest = getService('roleScopedSupertest'); + let supertestAdminWithCookieCredentials: SupertestWithRoleScope; + const mockAutoopsApiService = setupMockServer(); + describe('Metrics', function () { + let mockApiServer: http.Server; + // due to the plugin depending on yml config (xpack.dataUsage.enabled), we cannot test in MKI until it is on by default + this.tags(['skipMKI']); + + before(async () => { + mockApiServer = mockAutoopsApiService.listen(9000); + supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'admin', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); + }); + + after(() => { + mockApiServer.close(); + }); + describe(`POST ${DATA_USAGE_METRICS_API_ROUTE}`, () => { + const testDataStreamName = 'test-data-stream'; + before(async () => await svlDatastreamsHelpers.createDataStream(testDataStreamName)); + after(async () => await svlDatastreamsHelpers.deleteDataStream(testDataStreamName)); + it('returns 400 with non-existent data streams', async () => { + const requestBody: UsageMetricsRequestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: ['ingest_rate', 'storage_retained'], + dataStreams: ['invalid-data-stream'], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(400); + expect(res.body.message).to.be('Failed to retrieve data streams'); + }); + + it('returns 400 when requesting no data streams', async () => { + const requestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: ['ingest_rate'], + dataStreams: [], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(400); + expect(res.body.message).to.be('[request body.dataStreams]: no data streams selected'); + }); + + it('returns 400 when requesting an invalid metric type', async () => { + const requestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: [testDataStreamName], + dataStreams: ['datastream'], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(400); + expect(res.body.message).to.be( + '[request body.metricTypes]: must be one of ingest_rate, storage_retained, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate' + ); + }); + + it('returns 200 with valid request', async () => { + const requestBody: UsageMetricsRequestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: ['ingest_rate', 'storage_retained'], + dataStreams: [testDataStreamName], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(200); + expect(res.body).to.eql(transformMetricsData(mockAutoOpsResponse)); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts index 4d58d43ea7667..68202b5adf3ad 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Observability API Integration Tests - Common Group 1', diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts index 111686fd95d26..97a30d0f340f9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; import { services as apmServices } from './apm_api_integration/common/services'; import { services as datasetQualityServices } from './dataset_quality_api_integration/common/services'; @@ -26,5 +26,12 @@ export default createTestConfig({ '--xpack.uptime.service.manifestUrl=mockDevUrl', // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts index 4167364e44793..36249651d5167 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts @@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Search API Integration Tests - Common Group 1', diff --git a/x-pack/test_serverless/api_integration/test_suites/search/config.ts b/x-pack/test_serverless/api_integration/test_suites/search/config.ts index 94d7ea67c594a..9f02dc98b88c3 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -21,5 +22,12 @@ export default createTestConfig({ kbnServerArgs: [ // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts index 406de533a909d..577b4f8ba4ee0 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Security API Integration Tests - Common Group 1', diff --git a/x-pack/test_serverless/api_integration/test_suites/security/config.ts b/x-pack/test_serverless/api_integration/test_suites/security/config.ts index 0b24438b81591..52b933a22b086 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -25,5 +26,12 @@ export default createTestConfig({ '--coreApp.allowDynamicConfigOverrides=true', `--xpack.securitySolutionServerless.cloudSecurityUsageReportingTaskInterval=5s`, `--xpack.securitySolutionServerless.usageApi.url=http://localhost:8081`, + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/functional/page_objects/svl_management_page.ts b/x-pack/test_serverless/functional/page_objects/svl_management_page.ts index e77e77c4fa76c..5676975a89c08 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_management_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_management_page.ts @@ -59,5 +59,13 @@ export function SvlManagementPageProvider({ getService }: FtrProviderContext) { async clickSpacesManagementCard() { await testSubjects.click('app-card-spaces'); }, + + // Data Usage card + async assertDataUsageManagementCardExists() { + await testSubjects.existOrFail('app-card-data_usage'); + }, + async clickDataUsageManagementCard() { + await testSubjects.click('app-card-data_usage'); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts b/x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts new file mode 100644 index 0000000000000..dcdd23b13605f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext) => { + describe('Data Usage', function () { + loadTestFile(require.resolve('./main')); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts b/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts new file mode 100644 index 0000000000000..fa6e4199bdc09 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['svlCommonPage', 'svlManagementPage', 'common']); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + describe('Main page', function () { + this.tags(['skipMKI']); + before(async () => { + await pageObjects.svlCommonPage.loginAsAdmin(); + await pageObjects.common.navigateToApp('management'); + await retry.waitFor('page to be visible', async () => { + return await testSubjects.exists('cards-navigation-page'); + }); + await pageObjects.svlManagementPage.assertDataUsageManagementCardExists(); + await pageObjects.svlManagementPage.clickDataUsageManagementCard(); + }); + + after(async () => {}); + + it('renders data usage page', async () => { + await retry.waitFor('page to be visible', async () => { + return await testSubjects.exists('DataUsagePage'); + }); + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts b/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts index c75e2aa19be75..80f2313cf28a7 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts @@ -22,6 +22,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/painless_lab'), require.resolve('../../common/spaces'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Observability Functional Tests - Common Group 1', diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.ts b/x-pack/test_serverless/functional/test_suites/observability/config.ts index 2610af2a26149..9fffd5623f0a3 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/config.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -18,5 +18,13 @@ export default createTestConfig({ // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml esServerArgs: ['xpack.ml.dfa.enabled=false'], - kbnServerArgs: [], + kbnServerArgs: [ + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, + ], }); diff --git a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts index 3b0991b3b1fe9..11e36186609ec 100644 --- a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts +++ b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts @@ -20,6 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/reporting'), require.resolve('../../common/console'), require.resolve('../../common/spaces'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Search Functional Tests - Common Group 1', diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts index f330546373525..aef26951908d0 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -22,6 +22,13 @@ export default createTestConfig({ `--xpack.cloud.id=ES3_FTR_TESTS:ZmFrZS1kb21haW4uY2xkLmVsc3RjLmNvJGZha2Vwcm9qZWN0aWQuZXMkZmFrZXByb2plY3RpZC5rYg==`, `--xpack.cloud.serverless.project_name=ES3_FTR_TESTS`, `--xpack.cloud.deployment_url=/projects/elasticsearch/fakeprojectid`, + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], apps: { serverlessElasticsearch: { diff --git a/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts b/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts index 8adac3b9c4a6e..fc95c26547c0d 100644 --- a/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts +++ b/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts @@ -22,6 +22,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/painless_lab'), require.resolve('../../common/spaces'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Security Functional Tests - Common Group 1', diff --git a/x-pack/test_serverless/functional/test_suites/security/config.ts b/x-pack/test_serverless/functional/test_suites/security/config.ts index 2f6a4b4920a25..1693a07b0e844 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -18,4 +18,13 @@ export default createTestConfig({ // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/security/config/elasticsearch.yml esServerArgs: ['xpack.ml.nlp.enabled=true'], + kbnServerArgs: [ + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, + ], }); diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index f963f295cd17d..f54ffc1bb4c28 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -92,5 +92,6 @@ "@kbn/core-saved-objects-import-export-server-internal", "@kbn/security-plugin-types-common", "@kbn/ai-assistant-common", + "@kbn/data-usage-plugin", ] }