From 79c5e844ff288db593fcd6467ba560e75e884242 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 14 Nov 2024 15:46:28 +0100 Subject: [PATCH 01/33] Functional tests - remove .empty file from screenshots dir (#200161) ## Summary This PR removes the `.empty` file from the `test/functional/screenshots` directory as it's no longer needed. ### Details The file has been introduced as part of #14122 when the directory was cleaned. Since then, new base line screenshots have been added, so the directory is no longer empty. --- test/functional/screenshots/.empty | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/functional/screenshots/.empty diff --git a/test/functional/screenshots/.empty b/test/functional/screenshots/.empty deleted file mode 100644 index e69de29bb2d1d..0000000000000 From a21743cb44f8ca30968a6080483fd556c9958f83 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 14 Nov 2024 09:51:47 -0500 Subject: [PATCH 02/33] [Fleet] Fix OAS snapshot (#200186) ## Summary Fix OAS snapshot --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- oas_docs/output/kibana.serverless.yaml | 12 +++--------- oas_docs/output/kibana.yaml | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 32d38c3569148..9f0c38baded7d 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -16693,14 +16693,10 @@ paths: type: object properties: active: - description: >- - When false, the enrollment API key is revoked and - cannot be used for enrolling Elastic Agents. + description: When false, the enrollment API key is revoked and cannot be used for enrolling Elastic Agents. type: boolean api_key: - description: >- - The enrollment API key (token) used for enrolling - Elastic Agents. + description: The enrollment API key (token) used for enrolling Elastic Agents. type: string api_key_id: description: The ID of the API key in the Security API. @@ -16713,9 +16709,7 @@ paths: description: The name of the enrollment API key. type: string policy_id: - description: >- - The ID of the agent policy the Elastic Agent will be - enrolled in. + description: The ID of the agent policy the Elastic Agent will be enrolled in. type: string required: - id diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 8a3d4d3634b8d..50fd92fdc8a9c 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -19477,14 +19477,10 @@ paths: type: object properties: active: - description: >- - When false, the enrollment API key is revoked and - cannot be used for enrolling Elastic Agents. + description: When false, the enrollment API key is revoked and cannot be used for enrolling Elastic Agents. type: boolean api_key: - description: >- - The enrollment API key (token) used for enrolling - Elastic Agents. + description: The enrollment API key (token) used for enrolling Elastic Agents. type: string api_key_id: description: The ID of the API key in the Security API. @@ -19497,9 +19493,7 @@ paths: description: The name of the enrollment API key. type: string policy_id: - description: >- - The ID of the agent policy the Elastic Agent will be - enrolled in. + description: The ID of the agent policy the Elastic Agent will be enrolled in. type: string required: - id From fbd06a347f2d6b4c312848723ecdf490bfbf8feb Mon Sep 17 00:00:00 2001 From: Ania Kowalska <63072419+akowalska622@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:55:16 +0100 Subject: [PATCH 03/33] [Discover] fix: set smaller max width for mobile devices (#199798) --- .../public/dataview_picker/change_dataview.styles.ts | 8 +++++++- .../public/dataview_picker/change_dataview.tsx | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts b/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts index 9727c8f6e1593..21ece7f82e88d 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts @@ -12,15 +12,18 @@ import { calculateWidthFromEntries } from '@kbn/calculate-width-from-char-count' import { DataViewListItemEnhanced } from './dataview_list'; const MIN_WIDTH = 300; +const MAX_MOBILE_WIDTH = 350; export const changeDataViewStyles = ({ fullWidth, dataViewsList, theme, + isMobile, }: { fullWidth?: boolean; dataViewsList: DataViewListItemEnhanced[]; theme: EuiThemeComputed; + isMobile: boolean; }) => { return { trigger: { @@ -30,7 +33,10 @@ export const changeDataViewStyles = ({ borderBottomLeftRadius: 0, }, popoverContent: { - width: calculateWidthFromEntries(dataViewsList, ['name', 'id'], { minWidth: MIN_WIDTH }), + width: calculateWidthFromEntries(dataViewsList, ['name', 'id'], { + minWidth: MIN_WIDTH, + ...(isMobile && { maxWidth: MAX_MOBILE_WIDTH }), + }), }, }; }; diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index d89621d598679..06ed558ce7a01 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -17,6 +17,7 @@ import { EuiContextMenuItem, useEuiTheme, useGeneratedHtmlId, + useIsWithinBreakpoints, EuiIcon, EuiText, EuiContextMenuPanelProps, @@ -68,10 +69,13 @@ export function ChangeDataView({ const kibana = useKibana(); const { application, data, dataViews, dataViewEditor } = kibana.services; + const isMobile = useIsWithinBreakpoints(['xs']); + const styles = changeDataViewStyles({ fullWidth: trigger.fullWidth, dataViewsList, theme: euiTheme, + isMobile, }); // Create a reusable id to ensure search input is the first focused item in the popover even though it's not the first item From 1454a75986d7ddf4fa4414de399ba29aac50fa6e Mon Sep 17 00:00:00 2001 From: Tre Date: Thu, 14 Nov 2024 15:25:12 +0000 Subject: [PATCH 04/33] [Ownership] Assign test files to search kibana team (#200013) ## Summary Assign test files to search kibana team Contributes to: #192979 --- .github/CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 745101711171c..ff5ec12fef6a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1646,6 +1646,11 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/test/functional/es_archives/cases/signals/hosts_users @elastic/response-ops # Enterprise Search +# search +/x-pack/test/functional_solution_sidenav/tests/search_sidenav.ts @elastic/search-kibana +/x-pack/test/functional/services/search_sessions.ts @elastic/search-kibana +/x-pack/test/functional/page_objects/search_* @elastic/search-kibana +/x-pack/test/functional/apps/search_playground @elastic/search-kibana /x-pack/test_serverless/functional/page_objects/svl_ingest_pipelines.ts @elastic/search-kibana /x-pack/test/functional/apps/dev_tools/embedded_console.ts @elastic/search-kibana /x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts @elastic/search-kibana From 04b2d62fee8731e1a0a6ff230b512d7e94843ce5 Mon Sep 17 00:00:00 2001 From: Milosz Marcinkowski <38698566+miloszmarcinkowski@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:39:11 +0100 Subject: [PATCH 05/33] Migrate /diagnostics and /service_nodes to be deployment agnostic (#199645) closes #198967 closes #198985 part of https://github.com/elastic/kibana/issues/193245 ### How to test - Serverless ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep="APM" ``` - Stateful ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep="APM" ``` - [MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki) ### Checklist - [x] (OPTIONAL, only if a test has been unskipped) Run flaky test suite - [x] serverless - [x] stateful - [x] MKI --------- Co-authored-by: Sergi Romeu --- .../observability/apm/agent_explorer/index.ts | 2 +- .../apm/alerts/error_count_threshold.spec.ts | 2 +- .../apis/observability/apm/alerts/index.ts | 2 +- .../alerts/preview_chart_error_count.spec.ts | 2 +- .../alerts/preview_chart_error_rate.spec.ts | 2 +- .../observability/apm/cold_start/index.ts | 2 +- .../observability/apm/correlations/index.ts | 2 +- .../apm/custom_dashboards/index.ts | 2 +- .../apis/observability/apm/data_view/index.ts | 2 +- .../observability/apm/dependencies/index.ts | 2 +- .../apm/dependencies/top_dependencies.spec.ts | 2 +- .../apm}/diagnostics/apm_events.spec.ts | 16 +-- .../apm}/diagnostics/data_streams.spec.ts | 19 ++- .../observability/apm/diagnostics/index.ts | 19 +++ .../index_pattern_settings.spec.ts | 108 ++++++++++++++++++ .../apm}/diagnostics/index_templates.spec.ts | 40 ++----- .../apm}/diagnostics/indices.spec.ts | 21 ++-- .../apm}/diagnostics/privileges.spec.ts | 9 +- .../apis/observability/apm/entities/index.ts | 2 +- .../observability/apm/environment/index.ts | 2 +- .../observability/apm/error_rate/index.ts | 2 +- .../apm/error_rate/service_apis.spec.ts | 2 +- .../apm/error_rate/service_maps.spec.ts | 4 +- .../apm/historical_data/has_data.spec.ts | 4 +- .../apm/historical_data/index.ts | 2 +- .../apis/observability/apm/index.ts | 4 +- .../observability/apm/infrastructure/index.ts | 2 +- .../infrastructure_attributes.spec.ts | 2 +- .../apis/observability/apm/latency/index.ts | 2 +- .../apm/latency/service_apis.spec.ts | 4 +- .../apm/latency/service_maps.spec.ts | 4 +- .../apm/mobile/generate_mobile_data.ts | 2 +- .../apis/observability/apm/mobile/index.ts | 2 +- .../apm/observability_overview/index.ts | 2 +- .../observability_overview.spec.ts | 2 +- .../observability/apm/service_groups/index.ts | 2 +- .../service_groups/save_service_group.spec.ts | 2 +- .../service_group_count.spec.ts | 4 +- .../service_group_with_overflow.spec.ts | 4 +- .../service_nodes/get_service_nodes.spec.ts | 100 ++++++++++++++++ .../observability/apm/service_nodes/index.ts | 14 +++ .../diagnostics/index_pattern_settings.ts | 106 ----------------- .../service_nodes/get_service_nodes.spec.ts | 96 ---------------- 43 files changed, 321 insertions(+), 305 deletions(-) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/diagnostics/apm_events.spec.ts (93%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/diagnostics/data_streams.spec.ts (82%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_pattern_settings.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/diagnostics/index_templates.spec.ts (51%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/diagnostics/indices.spec.ts (88%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/diagnostics/privileges.spec.ts (90%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/get_service_nodes.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/index.ts delete mode 100644 x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts delete mode 100644 x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/index.ts index f77b13923930a..ae05cb6b89cbe 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('agent_explorer', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/error_count_threshold.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/error_count_threshold.spec.ts index e0b9e1b022b4f..c515263f09b2e 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/error_count_threshold.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/error_count_threshold.spec.ts @@ -10,7 +10,7 @@ import { errorCountActionVariables } from '@kbn/apm-plugin/server/routes/alerts/ import { apm, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; import { omit } from 'lodash'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import type { RoleCredentials } from '../../../../services'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/index.ts index 71661e4cbc8bc..3796b4253e60e 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('alerts', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_count.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_count.spec.ts index d6792400fc2bc..38bf9d4eade4d 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_count.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_count.spec.ts @@ -7,7 +7,7 @@ import type { PreviewChartResponseItem } from '@kbn/apm-plugin/server/routes/alerts/route'; import expect from '@kbn/expect'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { ERROR_GROUP_ID, SERVICE_ENVIRONMENT, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_rate.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_rate.spec.ts index 3e5c0753fbc1d..167b114e755c3 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_rate.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/alerts/preview_chart_error_rate.spec.ts @@ -13,7 +13,7 @@ import { } from '@kbn/observability-shared-plugin/common'; import type { PreviewChartResponseItem } from '@kbn/apm-plugin/server/routes/alerts/route'; import expect from '@kbn/expect'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { generateErrorData } from './generate_data'; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/cold_start/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/cold_start/index.ts index a5d8045f227d3..108340514a0c2 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/cold_start/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/cold_start/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('cold_start', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts index 660556edb8d79..ffa54dbe7bb5c 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/correlations/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('correlations', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/custom_dashboards/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/custom_dashboards/index.ts index 77f12ca0f88d5..a46024ff1ab10 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/custom_dashboards/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/custom_dashboards/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('custom_dashboards', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/data_view/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/data_view/index.ts index 9412ddca7cbcb..c81b643e62638 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/data_view/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/data_view/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('data_view', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts index 2acf449ce923d..46ad399380550 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('custom_dashboards', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/top_dependencies.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/top_dependencies.spec.ts index 0fa88b67d3379..21e990a1dbb52 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/top_dependencies.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/dependencies/top_dependencies.spec.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { NodeType, DependencyNode } from '@kbn/apm-plugin/common/connections'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { dataConfig, generateData } from './generate_data'; import { roundNumber } from '../utils/common'; diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/apm_events.spec.ts similarity index 93% rename from x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/apm_events.spec.ts index 1abace6c77f4c..4ca60e15fa190 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/apm_events.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/apm_events.spec.ts @@ -9,19 +9,18 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { sumBy } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); const es = getService('es'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); + const synthtrace = getService('synthtrace'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - // FLAKY: https://github.com/elastic/kibana/issues/177144 - registry.when('Diagnostics: APM Events', { config: 'basic', archives: [] }, () => { + describe('Diagnostics: APM Events', () => { describe('When there is no data', () => { before(async () => { // delete APM data streams @@ -38,10 +37,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('When data is ingested', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { const instance = apm .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) .instance('instance-a'); + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await apmSynthtraceEsClient.index( timerange(start, end) diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/data_streams.spec.ts similarity index 82% rename from x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/data_streams.spec.ts index 80fa34dbaa002..1d37c668e91f5 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/data_streams.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/data_streams.spec.ts @@ -7,20 +7,18 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); const es = getService('es'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - const synthtraceKibanaClient = getService('synthtraceKibanaClient'); + const synthtrace = getService('synthtrace'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - // FLAKY: https://github.com/elastic/kibana/issues/177245 - registry.when('Diagnostics: Data streams', { config: 'basic', archives: [] }, () => { + describe('Diagnostics: Data streams', () => { describe('When there is no data', () => { before(async () => { // delete APM data streams @@ -45,9 +43,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('When data is ingested', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { - const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion(); - await synthtraceKibanaClient.installApmPackage(latestVersion); + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); const instance = apm .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index.ts new file mode 100644 index 0000000000000..192771d3fc75e --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('diagnostics', () => { + loadTestFile(require.resolve('./apm_events.spec.ts')); + loadTestFile(require.resolve('./data_streams.spec.ts')); + loadTestFile(require.resolve('./index_pattern_settings.spec.ts')); + loadTestFile(require.resolve('./index_templates.spec.ts')); + loadTestFile(require.resolve('./indices.spec.ts')); + loadTestFile(require.resolve('./privileges.spec.ts')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_pattern_settings.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_pattern_settings.spec.ts new file mode 100644 index 0000000000000..8235e2a179d4c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_pattern_settings.spec.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { uniq } from 'lodash'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + describe('Diagnostics: Index pattern settings', () => { + describe('When data is ingested', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + + const instance = apm + .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) + .instance('instance-a'); + await apmSynthtraceEsClient.index( + timerange(start, end) + .interval('1m') + .rate(30) + .generator((timestamp) => + instance + .transaction({ transactionName: 'GET /users' }) + .timestamp(timestamp) + .duration(100) + .success() + ) + ); + }); + + after(() => apmSynthtraceEsClient.clean()); + + it('returns APM index templates', async () => { + const apmIndexTemplatesPatterns = ['apm', 'otel']; + + const { status, body } = await apmApiClient.adminUser({ + endpoint: 'GET /internal/apm/diagnostics', + }); + expect(status).to.be(200); + + // filtering the array for unique index templates because they get duplicated across different index patterns + const uniqueTemplateNames = uniq( + body.indexTemplatesByIndexPattern.flatMap(({ indexTemplates }) => { + return indexTemplates?.map(({ templateName }) => templateName); + }) + ); + + // filter only APM releated indices + const apmTemplateNames = uniqueTemplateNames.filter( + (templateName) => + templateName.endsWith('@template') && + apmIndexTemplatesPatterns.some((pattern) => templateName.includes(pattern)) + ); + + // sort alphabeticaly before comparing because an order is different between testing environments + const sortedApmTemplates = apmTemplateNames.sort(); + + expect(sortedApmTemplates).to.eql([ + 'logs-apm.app@template', + 'logs-apm.error@template', + 'logs-otel@template', + 'metrics-apm.app@template', + 'metrics-apm.internal@template', + 'metrics-apm.service_destination.10m@template', + 'metrics-apm.service_destination.1m@template', + 'metrics-apm.service_destination.60m@template', + 'metrics-apm.service_summary.10m@template', + 'metrics-apm.service_summary.1m@template', + 'metrics-apm.service_summary.60m@template', + 'metrics-apm.service_transaction.10m@template', + 'metrics-apm.service_transaction.1m@template', + 'metrics-apm.service_transaction.60m@template', + 'metrics-apm.transaction.10m@template', + 'metrics-apm.transaction.1m@template', + 'metrics-apm.transaction.60m@template', + 'metrics-otel@template', + 'metrics-service_summary.10m.otel@template', + 'metrics-service_summary.1m.otel@template', + 'metrics-service_summary.60m.otel@template', + 'metrics-service_transaction.10m.otel@template', + 'metrics-service_transaction.1m.otel@template', + 'metrics-service_transaction.60m.otel@template', + 'metrics-transaction.10m.otel@template', + 'metrics-transaction.1m.otel@template', + 'metrics-transaction.60m.otel@template', + 'traces-apm.rum@template', + 'traces-apm.sampled@template', + 'traces-apm@template', + 'traces-otel@template', + ]); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_templates.spec.ts similarity index 51% rename from x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_templates.spec.ts index 1bbc799b3bf78..aa45a93a3ce73 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/index_templates.spec.ts @@ -7,44 +7,22 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { getApmIndexTemplateNames } from '@kbn/apm-plugin/server/routes/diagnostics/helpers/get_apm_index_template_names'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const es = getService('es'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - const synthtraceKibanaClient = getService('synthtraceKibanaClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - registry.when.skip('Diagnostics: Index Templates', { config: 'basic', archives: [] }, () => { - describe('When there is no data', () => { - before(async () => { - // delete APM index templates - await es.indices.deleteIndexTemplate({ - name: Object.values(getApmIndexTemplateNames()).flat(), - }); - }); - - it('verifies that none of the default APM index templates exists`', async () => { - const { status, body } = await apmApiClient.adminUser({ - endpoint: 'GET /internal/apm/diagnostics', - }); - expect(status).to.be(200); - const noApmIndexTemplateExists = body.apmIndexTemplates.every( - ({ exists }) => exists === false - ); - expect(noApmIndexTemplateExists).to.eql(true); - }); - }); - + describe('Diagnostics: Index Templates', () => { describe('When data is ingested', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { - const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion(); - await synthtraceKibanaClient.installApmPackage(latestVersion); + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); const instance = apm .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts similarity index 88% rename from x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts index 477824524b48c..92976e6bce883 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/indices.spec.ts @@ -8,20 +8,20 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { omit } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const es = getService('es'); - const synthtraceKibanaClient = getService('synthtraceKibanaClient'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - // FLAKY: https://github.com/elastic/kibana/pull/177039 - registry.when.skip('Diagnostics: Indices', { config: 'basic', archives: [] }, () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + describe('Diagnostics: Indices', () => { describe.skip('When there is no data', () => { it('returns empty response`', async () => { const { status, body } = await apmApiClient.adminUser({ @@ -39,6 +39,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const instance = apm .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) .instance('instance-a'); + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await apmSynthtraceEsClient.index( timerange(start, end) @@ -92,8 +93,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await es.indices.delete({ index: 'traces-apm-default' }); - const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion(); - await synthtraceKibanaClient.installApmPackage(latestVersion); await apmSynthtraceEsClient.clean(); }); @@ -136,8 +135,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); after(async () => { - const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion(); - await synthtraceKibanaClient.installApmPackage(latestVersion); await apmSynthtraceEsClient.clean(); }); diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/privileges.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/privileges.spec.ts similarity index 90% rename from x-pack/test/apm_api_integration/tests/diagnostics/privileges.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/privileges.spec.ts index 2d9652b612010..fa46ff08cc8ed 100644 --- a/x-pack/test/apm_api_integration/tests/diagnostics/privileges.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/diagnostics/privileges.spec.ts @@ -7,13 +7,12 @@ import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); - registry.when('Diagnostics: Privileges', { config: 'basic', archives: [] }, () => { + describe('Diagnostics: Privileges', () => { describe('superuser', () => { let body: APIReturnType<'GET /internal/apm/diagnostics'>; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts index d7a36e3e447b7..71be4955fe523 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('entities', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/environment/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/environment/index.ts index 4a77e610d5000..e48a8f88d74c4 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/environment/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/environment/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('environment', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/index.ts index a3dd89f0ddb1a..04ed9b3be1ff0 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('error_rate', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_apis.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_apis.spec.ts index 56dded824a32d..6a8bca2eefaca 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_apis.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_apis.spec.ts @@ -13,7 +13,7 @@ import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; const GO_PROD_LIST_RATE = 75; const GO_PROD_LIST_ERROR_RATE = 25; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_maps.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_maps.spec.ts index 462ad8db4bdda..c45f3fd9457d8 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_maps.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/error_rate/service_maps.spec.ts @@ -5,13 +5,13 @@ * 2.0. */ import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import expect from '@kbn/expect'; import { meanBy } from 'lodash'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; const GO_PROD_LIST_RATE = 75; const GO_PROD_LIST_ERROR_RATE = 25; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/has_data.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/has_data.spec.ts index 6ac96b8e38154..b6eaca65cad1c 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/has_data.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/has_data.spec.ts @@ -8,8 +8,8 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import moment from 'moment'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { const apmApiClient = getService('apmApi'); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/index.ts index 49f0068ee313b..0b9c76d21d3b4 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/historical_data/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('historical_data', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts index af8cc32783c20..d115e10f2373a 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; export default function apmApiIntegrationTests({ loadTestFile, @@ -30,5 +30,7 @@ export default function apmApiIntegrationTests({ loadTestFile(require.resolve('./infrastructure')); loadTestFile(require.resolve('./inspect')); loadTestFile(require.resolve('./service_groups')); + loadTestFile(require.resolve('./diagnostics')); + loadTestFile(require.resolve('./service_nodes')); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/index.ts index 1351a44d41c8e..3379e3f0fdc23 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('infrastructure', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/infrastructure_attributes.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/infrastructure_attributes.spec.ts index ca70d8d1fa002..fe9b0246c505b 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/infrastructure_attributes.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/infrastructure/infrastructure_attributes.spec.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { generateData } from './generate_data'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { const apmApiClient = getService('apmApi'); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/index.ts index 0b9a71293f687..2ff9ec1896c78 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('latency', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_apis.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_apis.spec.ts index dee5f27b7a61d..2219a873c0f2d 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_apis.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_apis.spec.ts @@ -12,8 +12,8 @@ import { isFiniteNumber } from '@kbn/apm-plugin/common/utils/is_finite_number'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { const apmApiClient = getService('apmApi'); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_maps.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_maps.spec.ts index fa088e4f12dc9..e0bb69306a03f 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_maps.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/latency/service_maps.spec.ts @@ -10,8 +10,8 @@ import { meanBy } from 'lodash'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { const apmApiClient = getService('apmApi'); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/generate_mobile_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/generate_mobile_data.ts index a4420b3f53c7c..fa3323c411d61 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/generate_mobile_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/generate_mobile_data.ts @@ -5,7 +5,7 @@ * 2.0. */ import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; export const SERVICE_VERSIONS = ['2.3', '1.2', '1.1']; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts index 97d8e13256d60..d54eb187b29c6 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/mobile/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('Mobile', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/index.ts index c43e15d005bb9..bbb35976f8175 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('observability_overview', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/observability_overview.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/observability_overview.spec.ts index 740dd432b670b..796ef08f70250 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/observability_overview.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/observability_overview/observability_overview.spec.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { meanBy, sumBy } from 'lodash'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { roundNumber } from '../../../../../../apm_api_integration/utils'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/index.ts index e88208d48a9b5..458361077c208 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('service_groups', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/save_service_group.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/save_service_group.spec.ts index a0ed02739cf9f..0b4db96e880e3 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/save_service_group.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/save_service_group.spec.ts @@ -5,7 +5,7 @@ * 2.0. */ import expect from '@kbn/expect'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { ApmApiError } from '../../../../../../apm_api_integration/common/apm_api_supertest'; import { expectToReject } from '../../../../../../apm_api_integration/common/utils/expect_to_reject'; import { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_count/service_group_count.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_count/service_group_count.spec.ts index 21ac03197a422..cbb29e2729dcb 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_count/service_group_count.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_count/service_group_count.spec.ts @@ -5,8 +5,8 @@ * 2.0. */ import expect from '@kbn/expect'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; import { createServiceGroupApi, deleteAllServiceGroups, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts index 9324dee60d4e7..b5cbf1ae2566c 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_groups/service_group_with_overflow/service_group_with_overflow.spec.ts @@ -9,8 +9,8 @@ import { ValuesType } from 'utility-types'; import { ENVIRONMENT_ALL } from '@kbn/apm-plugin/common/environment_filter_values'; import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; import { createServiceGroupApi, deleteAllServiceGroups } from '../service_groups_api_methods'; import { createServiceTransactionMetricsDocs } from './es_utils'; import { generateData } from './generate_data'; diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/get_service_nodes.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/get_service_nodes.spec.ts new file mode 100644 index 0000000000000..95f0b8defa989 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/get_service_nodes.spec.ts @@ -0,0 +1,100 @@ +/* + * 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 { apm, timerange } from '@kbn/apm-synthtrace-client'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const serviceName = 'synth-go'; + const instanceName = 'instance-a'; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/nodes', + params: { + path: { serviceName }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + kuery: '', + environment: 'ENVIRONMENT_ALL', + }, + }, + }); + } + + describe('Service nodes', () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await callApi(); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "serviceNodes": Array [], + } + `); + }); + }); + + describe('when data is loaded', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + const instance = apm + .service({ name: serviceName, environment: 'production', agentName: 'go' }) + .instance(instanceName); + await apmSynthtraceEsClient.index( + timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => + instance + .appMetrics({ + 'system.process.cpu.total.norm.pct': 1, + 'jvm.memory.heap.used': 1000, + 'jvm.memory.non_heap.used': 100, + 'jvm.thread.count': 25, + }) + .timestamp(timestamp) + ) + ); + }); + after(() => apmSynthtraceEsClient.clean()); + + it('returns service nodes', async () => { + const response = await callApi(); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "serviceNodes": Array [ + Object { + "cpu": 1, + "heapMemory": 1000, + "hostName": "instance-a", + "name": "instance-a", + "nonHeapMemory": 100, + "threadCount": 25, + }, + ], + } + `); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/index.ts new file mode 100644 index 0000000000000..18315aade3b31 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/service_nodes/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 type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('service_nodes', () => { + loadTestFile(require.resolve('./get_service_nodes.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts b/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts deleted file mode 100644 index d0ba7b1850d31..0000000000000 --- a/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts +++ /dev/null @@ -1,106 +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 expect from '@kbn/expect'; -import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { getApmIndexTemplateNames } from '@kbn/apm-plugin/server/routes/diagnostics/helpers/get_apm_index_template_names'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const es = getService('es'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - const synthtraceKibanaClient = getService('synthtraceKibanaClient'); - - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - registry.when('Diagnostics: Index pattern settings', { config: 'basic', archives: [] }, () => { - describe('When there is no data', () => { - before(async () => { - // delete APM index templates - await es.indices.deleteIndexTemplate({ - name: Object.values(getApmIndexTemplateNames()).flat(), - }); - }); - - it('returns the built-in (non-APM) index templates`', async () => { - const { status, body } = await apmApiClient.adminUser({ - endpoint: 'GET /internal/apm/diagnostics', - }); - expect(status).to.be(200); - - const templateNames = body.indexTemplatesByIndexPattern.flatMap(({ indexTemplates }) => { - return indexTemplates?.map(({ templateName }) => templateName); - }); - - expect(templateNames).to.eql(['logs', 'metrics']); - }); - }); - - describe('When data is ingested', () => { - before(async () => { - const latestVersion = await synthtraceKibanaClient.fetchLatestApmPackageVersion(); - await synthtraceKibanaClient.installApmPackage(latestVersion); - - const instance = apm - .service({ name: 'synth-go', environment: 'production', agentName: 'go' }) - .instance('instance-a'); - await apmSynthtraceEsClient.index( - timerange(start, end) - .interval('1m') - .rate(30) - .generator((timestamp) => - instance - .transaction({ transactionName: 'GET /users' }) - .timestamp(timestamp) - .duration(100) - .success() - ) - ); - }); - - after(() => apmSynthtraceEsClient.clean()); - - it('returns APM index templates', async () => { - const { status, body } = await apmApiClient.adminUser({ - endpoint: 'GET /internal/apm/diagnostics', - }); - expect(status).to.be(200); - - const templateNames = body.indexTemplatesByIndexPattern.flatMap(({ indexTemplates }) => { - return indexTemplates?.map(({ templateName }) => templateName); - }); - - expect(templateNames).to.eql([ - 'logs-apm.error', - 'logs-apm.app', - 'logs', - 'metrics-apm.service_transaction.60m', - 'metrics-apm.service_destination.10m', - 'metrics-apm.transaction.1m', - 'metrics-apm.service_destination.1m', - 'metrics-apm.service_transaction.10m', - 'metrics-apm.service_transaction.1m', - 'metrics-apm.transaction.60m', - 'metrics-apm.service_destination.60m', - 'metrics-apm.service_summary.1m', - 'metrics-apm.transaction.10m', - 'metrics-apm.internal', - 'metrics-apm.service_summary.10m', - 'metrics-apm.service_summary.60m', - 'metrics-apm.app', - 'metrics', - 'traces-apm', - 'traces-apm.rum', - 'traces-apm.sampled', - ]); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts deleted file mode 100644 index 6b24587b6bc13..0000000000000 --- a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts +++ /dev/null @@ -1,96 +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 expect from '@kbn/expect'; -import { apm, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const apmApiClient = getService('apmApiClient'); - const registry = getService('registry'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - const serviceName = 'synth-go'; - const instanceName = 'instance-a'; - - async function callApi() { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/metrics/nodes', - params: { - path: { serviceName }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - kuery: '', - environment: 'ENVIRONMENT_ALL', - }, - }, - }); - } - - registry.when('Service nodes when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles the empty state', async () => { - const response = await callApi(); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "serviceNodes": Array [], - } - `); - }); - }); - - // FLAKY: https://github.com/elastic/kibana/issues/177496 - registry.when('Service nodes when data is loaded', { config: 'basic', archives: [] }, () => { - before(async () => { - const instance = apm - .service({ name: serviceName, environment: 'production', agentName: 'go' }) - .instance(instanceName); - await apmSynthtraceEsClient.index( - timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => - instance - .appMetrics({ - 'system.process.cpu.total.norm.pct': 1, - 'jvm.memory.heap.used': 1000, - 'jvm.memory.non_heap.used': 100, - 'jvm.thread.count': 25, - }) - .timestamp(timestamp) - ) - ); - }); - after(() => apmSynthtraceEsClient.clean()); - - it('returns service nodes', async () => { - const response = await callApi(); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "serviceNodes": Array [ - Object { - "cpu": 1, - "heapMemory": 1000, - "hostName": "instance-a", - "name": "instance-a", - "nonHeapMemory": 100, - "threadCount": 25, - }, - ], - } - `); - }); - }); -} From f3e773552d2f98d26afd8e9df866c812a5e6fdd7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 14 Nov 2024 08:43:56 -0700 Subject: [PATCH 06/33] [presentation-utils] remove defaultTheme$ (#200044) Closes https://github.com/elastic/kibana/issues/200037 defaultTheme$ is only used in storybooks. This PR removes defaultTheme$ and replaces it with theme$ available from core start mock. --- .../__stories__/error_renderer.stories.tsx | 6 +++++- .../expression_renderers/debug_renderer.tsx | 4 +--- .../expression_renderers/error_renderer.tsx | 4 +--- .../__stories__/image_renderer.stories.tsx | 10 +++++++++- .../expression_renderers/image_renderer.tsx | 5 ++--- .../__stories__/metric_renderer.stories.tsx | 17 ++++++++++------- .../expression_renderers/metric_renderer.tsx | 4 +--- .../repeat_image_renderer.stories.tsx | 9 ++++++++- .../repeat_image_renderer.tsx | 5 ++--- .../reveal_image_renderer.stories.tsx | 9 ++++++++- .../reveal_image_renderer.tsx | 4 +--- .../__stories__/progress_renderer.stories.tsx | 5 ++++- .../__stories__/shape_renderer.stories.tsx | 5 ++++- .../expression_renderers/progress_renderer.tsx | 4 +--- .../expression_renderers/shape_renderer.tsx | 4 +--- src/plugins/presentation_util/common/index.ts | 1 - .../common/lib/utils/default_theme.ts | 15 --------------- .../presentation_util/common/lib/utils/index.ts | 1 - 18 files changed, 58 insertions(+), 54 deletions(-) delete mode 100644 src/plugins/presentation_util/common/lib/utils/default_theme.ts diff --git a/src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx b/src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx index 07451f1f2c325..b7f789f873de3 100644 --- a/src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx +++ b/src/plugins/expression_error/public/expression_renderers/__stories__/error_renderer.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import { coreMock } from '@kbn/core/public/mocks'; import { Render } from '@kbn/presentation-util-plugin/public/__stories__'; import { getErrorRenderer } from '../error_renderer'; @@ -17,5 +18,8 @@ storiesOf('renderers/error', module).add('default', () => { const config = { error: thrownError, }; - return ; + + return ( + + ); }); diff --git a/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx b/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx index c3d4220038870..29fa69aa736b4 100644 --- a/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx +++ b/src/plugins/expression_error/public/expression_renderers/debug_renderer.tsx @@ -17,7 +17,6 @@ import { i18n } from '@kbn/i18n'; import { withSuspense } from '@kbn/presentation-util-plugin/public'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; -import { defaultTheme$ } from '@kbn/presentation-util-plugin/common'; import { JSON } from '../../common'; import { LazyDebugRenderComponent } from '../components'; @@ -38,8 +37,7 @@ const strings = { }; export const getDebugRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'debug', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx b/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx index c7865525b21f6..4ba3daa15d08c 100644 --- a/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx +++ b/src/plugins/expression_error/public/expression_renderers/error_renderer.tsx @@ -21,7 +21,6 @@ import { import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; import { withSuspense } from '@kbn/presentation-util-plugin/public'; -import { defaultTheme$ } from '@kbn/presentation-util-plugin/common'; import { ErrorRendererConfig } from '../../common/types'; import { LazyErrorRenderComponent } from '../components'; @@ -39,8 +38,7 @@ const errorStrings = { const ErrorComponent = withSuspense(LazyErrorRenderComponent); export const getErrorRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'error', displayName: errorStrings.getDisplayName(), help: errorStrings.getHelpDescription(), diff --git a/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx b/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx index 02679899b112d..07fb4db558bd5 100644 --- a/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx +++ b/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import { coreMock } from '@kbn/core/public/mocks'; import { Render, waitFor } from '@kbn/presentation-util-plugin/public/__stories__'; import { getElasticLogo } from '@kbn/presentation-util-plugin/common'; import { getImageRenderer } from '../image_renderer'; @@ -20,7 +21,14 @@ const Renderer = ({ elasticLogo }: { elasticLogo: string }) => { mode: ImageMode.COVER, }; - return ; + return ( + + ); }; storiesOf('renderers/image', module).add( diff --git a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx index 8954bd90dc974..e80281fc10f42 100644 --- a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx +++ b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx @@ -17,7 +17,7 @@ import { IInterpreterRenderHandlers, } from '@kbn/expressions-plugin/common'; import { i18n } from '@kbn/i18n'; -import { getElasticLogo, defaultTheme$, isValidUrl } from '@kbn/presentation-util-plugin/common'; +import { getElasticLogo, isValidUrl } from '@kbn/presentation-util-plugin/common'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; import { ImageRendererConfig } from '../../common/types'; @@ -34,8 +34,7 @@ const strings = { }; export const getImageRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'image', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx b/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx index 03c8f9d5048d5..5438bb4b4287a 100644 --- a/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx +++ b/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx @@ -9,6 +9,7 @@ import React, { CSSProperties } from 'react'; import { storiesOf } from '@storybook/react'; +import { coreMock } from '@kbn/core/public/mocks'; import { Style } from '@kbn/expressions-plugin/common'; import { Render } from '@kbn/presentation-util-plugin/public/__stories__'; import { getMetricRenderer } from '../metric_renderer'; @@ -37,6 +38,8 @@ const metricFontSpec: CSSProperties = { color: '#b83c6f', }; +const theme$ = coreMock.createStart().theme.theme$; + storiesOf('renderers/Metric', module) .add('with null metric', () => { const config: MetricRendererConfig = { @@ -46,7 +49,7 @@ storiesOf('renderers/Metric', module) label: '', metricFormat: '', }; - return ; + return ; }) .add('with number metric', () => { const config: MetricRendererConfig = { @@ -56,7 +59,7 @@ storiesOf('renderers/Metric', module) label: '', metricFormat: '', }; - return ; + return ; }) .add('with string metric', () => { const config: MetricRendererConfig = { @@ -66,7 +69,7 @@ storiesOf('renderers/Metric', module) label: '', metricFormat: '', }; - return ; + return ; }) .add('with label', () => { const config: MetricRendererConfig = { @@ -76,7 +79,7 @@ storiesOf('renderers/Metric', module) label: 'Average price', metricFormat: '', }; - return ; + return ; }) .add('with number metric and a specified format', () => { const config: MetricRendererConfig = { @@ -86,7 +89,7 @@ storiesOf('renderers/Metric', module) label: 'Average price', metricFormat: '0.00%', }; - return ; + return ; }) .add('with formatted string metric and a specified format', () => { const config: MetricRendererConfig = { @@ -96,7 +99,7 @@ storiesOf('renderers/Metric', module) label: 'Total Revenue', metricFormat: '$0a', }; - return ; + return ; }) .add('with invalid metricFont', () => { const config: MetricRendererConfig = { @@ -106,5 +109,5 @@ storiesOf('renderers/Metric', module) label: 'Total Revenue', metricFormat: '$0a', }; - return ; + return ; }); diff --git a/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx b/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx index b802f4f40dafb..db4b8575d5e26 100644 --- a/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx +++ b/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx @@ -19,7 +19,6 @@ import { import { i18n } from '@kbn/i18n'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; -import { defaultTheme$ } from '@kbn/presentation-util-plugin/common'; import { MetricRendererConfig } from '../../common/types'; const strings = { @@ -34,8 +33,7 @@ const strings = { }; export const getMetricRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'metric', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx b/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx index fa666d98ebb9a..a5f58b97970cf 100644 --- a/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx +++ b/src/plugins/expression_repeat_image/public/expression_renderers/__stories__/repeat_image_renderer.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import { coreMock } from '@kbn/core/public/mocks'; import { Render } from '@kbn/presentation-util-plugin/public/__stories__'; import { getElasticLogo, getElasticOutline } from '@kbn/presentation-util-plugin/common'; import { waitFor } from '@kbn/presentation-util-plugin/public/__stories__'; @@ -29,7 +30,13 @@ const Renderer = ({ emptyImage: elasticOutline, }; - return ; + return ( + + ); }; storiesOf('enderers/repeatImage', module).add( diff --git a/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx b/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx index 6dcc132937060..9a35459880889 100644 --- a/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx +++ b/src/plugins/expression_repeat_image/public/expression_renderers/repeat_image_renderer.tsx @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; -import { defaultTheme$, getElasticOutline, isValidUrl } from '@kbn/presentation-util-plugin/common'; +import { getElasticOutline, isValidUrl } from '@kbn/presentation-util-plugin/common'; import { RepeatImageRendererConfig } from '../../common/types'; const strings = { @@ -35,8 +35,7 @@ const strings = { }; export const getRepeatImageRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'repeatImage', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx index 0c1a2cb5ca169..664cc97117791 100644 --- a/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx +++ b/src/plugins/expression_reveal_image/public/expression_renderers/__stories__/reveal_image_renderer.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import { coreMock } from '@kbn/core/public/mocks'; import { getElasticOutline, getElasticLogo } from '@kbn/presentation-util-plugin/common'; import { Render, waitFor } from '@kbn/presentation-util-plugin/public/__stories__'; import { getRevealImageRenderer } from '..'; @@ -27,7 +28,13 @@ const Renderer = ({ origin: Origin.LEFT, percent: 0.45, }; - return ; + + return ( + + ); }; storiesOf('renderers/revealImage', module).add( diff --git a/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx index 5beb58ef60f39..fbfe479225ece 100644 --- a/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx +++ b/src/plugins/expression_reveal_image/public/expression_renderers/reveal_image_renderer.tsx @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; -import { defaultTheme$ } from '@kbn/presentation-util-plugin/common'; import { RevealImageRendererConfig } from '../../common/types'; export const strings = { @@ -35,8 +34,7 @@ export const strings = { }; export const getRevealImageRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'revealImage', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx b/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx index 22e8864d1b7de..0f93314ee0816 100644 --- a/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx +++ b/src/plugins/expression_shape/public/expression_renderers/__stories__/progress_renderer.stories.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { Render } from '@kbn/presentation-util-plugin/public/__stories__'; +import { coreMock } from '@kbn/core/public/mocks'; import { getProgressRenderer } from '../progress_renderer'; import { Progress } from '../../../common'; @@ -30,5 +31,7 @@ storiesOf('renderers/progress', module).add('default', () => { valueWeight: 15, }; - return ; + return ( + + ); }); diff --git a/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx b/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx index 77b54ae87239b..d089174c60325 100644 --- a/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx +++ b/src/plugins/expression_shape/public/expression_renderers/__stories__/shape_renderer.stories.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; +import { coreMock } from '@kbn/core/public/mocks'; import { Render } from '@kbn/presentation-util-plugin/public/__stories__'; import { getShapeRenderer } from '..'; import { Shape } from '../../../common/types'; @@ -23,5 +24,7 @@ storiesOf('renderers/shape', module).add('default', () => { maintainAspect: true, }; - return ; + return ( + + ); }); diff --git a/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx b/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx index 80904cf12f2b6..ed7629a7d87a0 100644 --- a/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx +++ b/src/plugins/expression_shape/public/expression_renderers/progress_renderer.tsx @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; -import { defaultTheme$ } from '@kbn/presentation-util-plugin/common'; import { ProgressRendererConfig } from '../../common/types'; const strings = { @@ -35,8 +34,7 @@ const strings = { }; export const getProgressRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'progress', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx b/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx index 06c442ad2d8cd..650033aa4542d 100644 --- a/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx +++ b/src/plugins/expression_shape/public/expression_renderers/shape_renderer.tsx @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; -import { defaultTheme$ } from '@kbn/presentation-util-plugin/common'; import { ShapeRendererConfig } from '../../common/types'; const strings = { @@ -35,8 +34,7 @@ const strings = { }; export const getShapeRenderer = - (theme$: Observable = defaultTheme$) => - (): ExpressionRenderDefinition => ({ + (theme$: Observable) => (): ExpressionRenderDefinition => ({ name: 'shape', displayName: strings.getDisplayName(), help: strings.getHelpDescription(), diff --git a/src/plugins/presentation_util/common/index.ts b/src/plugins/presentation_util/common/index.ts index 6917c52f4edc9..b0350ca4a20f4 100644 --- a/src/plugins/presentation_util/common/index.ts +++ b/src/plugins/presentation_util/common/index.ts @@ -36,7 +36,6 @@ export { } from './labs'; export { - defaultTheme$, getElasticLogo, getElasticOutline, isValidUrl, diff --git a/src/plugins/presentation_util/common/lib/utils/default_theme.ts b/src/plugins/presentation_util/common/lib/utils/default_theme.ts deleted file mode 100644 index 9c86456bc2513..0000000000000 --- a/src/plugins/presentation_util/common/lib/utils/default_theme.ts +++ /dev/null @@ -1,15 +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 type { CoreTheme } from '@kbn/core/public'; -import { Observable } from 'rxjs'; - -export const defaultTheme$: Observable = new Observable((subscriber) => - subscriber.next({ darkMode: false }) -); diff --git a/src/plugins/presentation_util/common/lib/utils/index.ts b/src/plugins/presentation_util/common/lib/utils/index.ts index 20b1d8450e5a5..091aa216aa835 100644 --- a/src/plugins/presentation_util/common/lib/utils/index.ts +++ b/src/plugins/presentation_util/common/lib/utils/index.ts @@ -11,7 +11,6 @@ export * from './dataurl'; export * from './httpurl'; export * from './resolve_dataurl'; export * from './url'; -export { defaultTheme$ } from './default_theme'; export async function getElasticLogo() { return await import('./elastic_logo'); From 50f0016cd7b01eabc280aca4131f843ff305231d Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 14 Nov 2024 07:54:51 -0800 Subject: [PATCH 07/33] [OpenAPI][DOCS] Add descriptions for alerting rule flapping properties (#200112) --- oas_docs/bundle.json | 18 ++++++ oas_docs/bundle.serverless.json | 18 ++++++ oas_docs/output/kibana.serverless.yaml | 60 +++++++++++++++++++ oas_docs/output/kibana.yaml | 60 +++++++++++++++++++ .../routes/rule/common/flapping/schemas/v1.ts | 13 +++- 5 files changed, 168 insertions(+), 1 deletion(-) diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 0fde92c1fa3f9..a4c1e30c8cf05 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -1304,14 +1304,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -2083,14 +2086,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -2484,14 +2490,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -3253,14 +3262,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -3647,14 +3659,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -5094,14 +5109,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 229bda84b8629..a0bd0c4cc4340 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -1304,14 +1304,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -2083,14 +2086,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -2484,14 +2490,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -3253,14 +3262,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -3647,14 +3659,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" @@ -5094,14 +5109,17 @@ }, "flapping": { "additionalProperties": false, + "description": "When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.", "nullable": true, "properties": { "look_back_window": { + "description": "The minimum number of runs in which the threshold must be met.", "maximum": 20, "minimum": 2, "type": "number" }, "status_change_threshold": { + "description": "The minimum number of times an alert must switch states in the look back window.", "maximum": 20, "minimum": 2, "type": "number" diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 9f0c38baded7d..117e52586c5ad 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -1018,14 +1018,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number @@ -1600,14 +1610,24 @@ paths: type: boolean flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch states + in the look back window. maximum: 20 minimum: 2 type: number @@ -1925,14 +1945,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number @@ -2510,14 +2540,24 @@ paths: - active flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch states + in the look back window. maximum: 20 minimum: 2 type: number @@ -2807,14 +2847,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number @@ -3852,14 +3902,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 50fd92fdc8a9c..82acbdb311f2f 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -1367,14 +1367,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number @@ -1948,14 +1958,24 @@ paths: type: boolean flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch states + in the look back window. maximum: 20 minimum: 2 type: number @@ -2273,14 +2293,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number @@ -2857,14 +2887,24 @@ paths: - active flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch states + in the look back window. maximum: 20 minimum: 2 type: number @@ -3154,14 +3194,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number @@ -4191,14 +4241,24 @@ paths: - last_execution_date flapping: additionalProperties: false + description: >- + When flapping detection is turned on, alerts that switch + quickly between active and recovered states are identified + as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: + description: >- + The minimum number of runs in which the threshold must + be met. maximum: 20 minimum: 2 type: number status_change_threshold: + description: >- + The minimum number of times an alert must switch + states in the look back window. maximum: 20 minimum: 2 type: number diff --git a/x-pack/plugins/alerting/common/routes/rule/common/flapping/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/common/flapping/schemas/v1.ts index 4844a9e808ef5..acdd35854df3f 100644 --- a/x-pack/plugins/alerting/common/routes/rule/common/flapping/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/common/flapping/schemas/v1.ts @@ -17,13 +17,24 @@ import { validateFlapping as validateFlappingV1 } from '../../../validation/vali export const flappingSchema = schema.object( { look_back_window: schema.number({ + meta: { description: 'The minimum number of runs in which the threshold must be met.' }, min: MIN_LOOK_BACK_WINDOW_V1, max: MAX_LOOK_BACK_WINDOW_V1, }), status_change_threshold: schema.number({ + meta: { + description: + 'The minimum number of times an alert must switch states in the look back window.', + }, min: MIN_STATUS_CHANGE_THRESHOLD_V1, max: MAX_STATUS_CHANGE_THRESHOLD_V1, }), }, - { validate: validateFlappingV1 } + { + validate: validateFlappingV1, + meta: { + description: + 'When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced.', + }, + } ); From 53c05a33e7fe917b7d64b7d3ca664aaf3f9cf3ca Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Thu, 14 Nov 2024 11:09:42 -0500 Subject: [PATCH 08/33] [Obs AI Assistant] Add retry statements as an attempt to resolve flaky tests (#200022) Closes https://github.com/elastic/kibana/issues/192222 ## Summary ### Problem The test appears to be flaky, potentially because the entries are not available at the time of retrieval. This cannot be reproduced locally or via the flaky test runner. (more details [here](https://github.com/elastic/kibana/pull/196026#issuecomment-2409056856)) ### Solution Add a retry when fetching the instructions and check whether the number of instructions returned by the API endpoint is the same number of instructions expected. ### Checklist - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --- .../knowledge_base_user_instructions.spec.ts | 104 ++++++++++-------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index 04791909340ef..6ea2b279fd386 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -30,6 +30,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const es = getService('es'); const ml = getService('ml'); const log = getService('log'); + const retry = getService('retry'); const getScopedApiClientForUsername = getService('getScopedApiClientForUsername'); describe('Knowledge base user instructions', () => { @@ -94,62 +95,69 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('"editor" can retrieve their own private instructions and the public instruction', async () => { - const res = await observabilityAIAssistantAPIClient.editor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', - }); + await retry.try(async () => { + const res = await observabilityAIAssistantAPIClient.editor({ + endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', + }); - const instructions = res.body.userInstructions; + const instructions = res.body.userInstructions; + expect(instructions).to.have.length(3); - const sortById = (data: Array) => sortBy(data, 'id'); + const sortById = (data: Array) => sortBy(data, 'id'); - expect(sortById(instructions)).to.eql( - sortById([ - { - id: 'private-doc-from-editor', - public: false, - text: 'Private user instruction from "editor"', - }, - { - id: 'public-doc-from-editor', - public: true, - text: 'Public user instruction from "editor"', - }, - { - id: 'public-doc-from-secondary_editor', - public: true, - text: 'Public user instruction from "secondary_editor"', - }, - ]) - ); + expect(sortById(instructions)).to.eql( + sortById([ + { + id: 'private-doc-from-editor', + public: false, + text: 'Private user instruction from "editor"', + }, + { + id: 'public-doc-from-editor', + public: true, + text: 'Public user instruction from "editor"', + }, + { + id: 'public-doc-from-secondary_editor', + public: true, + text: 'Public user instruction from "secondary_editor"', + }, + ]) + ); + }); }); it('"secondaryEditor" can retrieve their own private instructions and the public instruction', async () => { - const res = await observabilityAIAssistantAPIClient.secondaryEditor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', - }); - const instructions = res.body.userInstructions; + await retry.try(async () => { + const res = await observabilityAIAssistantAPIClient.secondaryEditor({ + endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', + }); - const sortById = (data: Array) => sortBy(data, 'id'); + const instructions = res.body.userInstructions; + expect(instructions).to.have.length(3); - expect(sortById(instructions)).to.eql( - sortById([ - { - id: 'public-doc-from-editor', - public: true, - text: 'Public user instruction from "editor"', - }, - { - id: 'public-doc-from-secondary_editor', - public: true, - text: 'Public user instruction from "secondary_editor"', - }, - { - id: 'private-doc-from-secondary_editor', - public: false, - text: 'Private user instruction from "secondary_editor"', - }, - ]) - ); + const sortById = (data: Array) => sortBy(data, 'id'); + + expect(sortById(instructions)).to.eql( + sortById([ + { + id: 'public-doc-from-editor', + public: true, + text: 'Public user instruction from "editor"', + }, + { + id: 'public-doc-from-secondary_editor', + public: true, + text: 'Public user instruction from "secondary_editor"', + }, + { + id: 'private-doc-from-secondary_editor', + public: false, + text: 'Private user instruction from "secondary_editor"', + }, + ]) + ); + }); }); }); From 20953fcb6f9476a5dfa89b581ee65a59c319a8f3 Mon Sep 17 00:00:00 2001 From: wajihaparvez Date: Thu, 14 Nov 2024 12:02:01 -0500 Subject: [PATCH 09/33] [Docs] Add breaking change to release notes (#200065) ## Summary Adding a Kibana-related breaking change found in the Security release notes. --- docs/CHANGELOG.asciidoc | 17 +++++++++++++++-- docs/upgrade-notes.asciidoc | 9 +++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 0c9bd6ada3904..1b45c8700d9a3 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -156,9 +156,22 @@ The Observability AI Assistant specific advanced setting for Logs index patterns //*Impact* + //!!TODO!! ==== - - +[float] +[[breaking-changes-8.16.0]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.16.0, review the breaking changes, then mitigate the impact to your application. + +[discrete] +.Updated request processing during shutdown. +[%collapsible] +==== +*Details* + +During shutdown, {kib} now waits for all the ongoing requests to complete according to the `server.shutdownTimeout` setting. During that period, the incoming socket is closed and any new incoming requests are rejected. Before this update, new incoming requests received a response with the status code 503 and body `{"message": "Kibana is shutting down and not accepting new incoming requests"}`. For more information, refer to {kibana-pull}180986[#180986]. +==== + [float] [[features-8.16.0]] === Features diff --git a/docs/upgrade-notes.asciidoc b/docs/upgrade-notes.asciidoc index a0c2d6c1afccb..c2d866f90eed3 100644 --- a/docs/upgrade-notes.asciidoc +++ b/docs/upgrade-notes.asciidoc @@ -434,6 +434,15 @@ The endpoint is now split into two separate endpoints: // General settings +[discrete] +[[breaking-180986]] +.[General settings] Updated request processing during shutdown. (8.16) +[%collapsible] +==== +*Details* + +During shutdown, {kib} now waits for all the ongoing requests to complete according to the `server.shutdownTimeout` setting. During that period, the incoming socket is closed and any new incoming requests are rejected. Before this update, new incoming requests received a response with the status code 503 and body `{"message": "Kibana is shutting down and not accepting new incoming requests"}`. For more information, refer to {kibana-pull}180986[#180986]. +==== + [discrete] [[breaking-111535]] .[General settings] Removed `CONFIG_PATH` and `DATA_PATH` environment variables. (8.0) From e2702ff5912ec440060d62fb323a9a03c4881143 Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 14 Nov 2024 18:05:11 +0100 Subject: [PATCH 10/33] [ResponseOps] [Cases] Attach file to case API (#198377) Fixes #22832 ## Summary This PR adds the possibility of adding Files/Attachments to Case in Kibana via an API call. ### How to test The new API URL is `https://localhost:5601/api/cases//files`. You can either use postman or curl to test. 1. Start by creating a case. 2. Call the new API ``` curl --location 'https://localhost:5601/api/cases//files' \ --header 'kbn-xsrf: true' \ --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \ --form 'filename="Notice"' \ --form 'mimeType="text/plain"' \ --form 'file=@""' ``` Screenshot 2024-10-30 at 15 41 26 3. Confirm the user action was created. Screenshot 2024-10-30 at 15 48 45 4. Confirm the file exists in the case and: - it can be downloaded as expected. - it can be previewed as expected(not every MIME type allows this). ### Release Notes Files can now be attached to cases directly via API. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: lcawl --- oas_docs/output/kibana.yaml | 59 ++++++ .../plugins/cases/common/constants/index.ts | 3 + .../plugins/cases/common/schema/index.test.ts | 27 ++- x-pack/plugins/cases/common/schema/index.ts | 15 ++ .../common/types/api/attachment/v1.test.ts | 52 ++++- .../cases/common/types/api/attachment/v1.ts | 17 ++ .../common/types/domain/attachment/v1.ts | 7 + .../plugins/cases/docs/openapi/bundled.json | 74 ++++++++ .../plugins/cases/docs/openapi/bundled.yaml | 49 +++++ .../schemas/add_case_file_request.yaml | 13 ++ .../cases/docs/openapi/entrypoint.yaml | 72 +++---- .../paths/api@cases@{caseid}@files.yaml | 40 ++++ .../client/attachments/add_file.test.ts | 129 +++++++++++++ .../server/client/attachments/add_file.ts | 109 +++++++++++ .../cases/server/client/attachments/client.ts | 7 + .../cases/server/client/attachments/types.ts | 36 +++- x-pack/plugins/cases/server/client/mocks.ts | 1 + .../plugins/cases/server/client/utils.test.ts | 38 ++++ x-pack/plugins/cases/server/client/utils.ts | 31 +++ .../server/routes/api/files/post_file.test.ts | 115 +++++++++++ .../server/routes/api/files/post_file.ts | 92 +++++++++ .../server/routes/api/get_external_routes.ts | 2 + .../common/lib/api/attachments.ts | 28 +++ .../cases_api_integration/common/lib/mock.ts | 10 +- .../tests/common/cases/delete_cases.ts | 3 + .../tests/common/files/post_file.ts | 179 ++++++++++++++++++ .../security_and_spaces/tests/common/index.ts | 1 + .../tests/common/files/post_file.ts | 86 +++++++++ .../spaces_only/tests/common/index.ts | 1 + 29 files changed, 1254 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/cases/docs/openapi/components/schemas/add_case_file_request.yaml create mode 100644 x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@files.yaml create mode 100644 x-pack/plugins/cases/server/client/attachments/add_file.test.ts create mode 100644 x-pack/plugins/cases/server/client/attachments/add_file.ts create mode 100644 x-pack/plugins/cases/server/routes/api/files/post_file.test.ts create mode 100644 x-pack/plugins/cases/server/routes/api/files/post_file.ts create mode 100644 x-pack/test/cases_api_integration/security_and_spaces/tests/common/files/post_file.ts create mode 100644 x-pack/test/cases_api_integration/spaces_only/tests/common/files/post_file.ts diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 82acbdb311f2f..ceefaa13fcd4b 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -6706,6 +6706,46 @@ paths: summary: Push a case to an external service tags: - cases + /api/cases/{caseId}/files: + post: + description: > + Attach a file to a case. You must have `all` privileges for the + **Cases** feature in the **Management**, **Observability**, or + **Security** section of the Kibana feature privileges, depending on the + owner of the case you're updating. The request must include: + + - The `Content-Type: multipart/form-data` HTTP header. + + - The location of the file that is being uploaded. + operationId: addCaseFileDefaultSpace + parameters: + - $ref: '#/components/parameters/Cases_kbn_xsrf' + - $ref: '#/components/parameters/Cases_case_id' + requestBody: + content: + multipart/form-data; Elastic-Api-Version=2023-10-31: + schema: + $ref: '#/components/schemas/Cases_add_case_file_request' + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + examples: + addCaseFileResponse: + $ref: '#/components/examples/Cases_add_comment_response' + schema: + $ref: '#/components/schemas/Cases_case_response_properties' + description: Indicates a successful call. + '401': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + $ref: '#/components/schemas/Cases_4xx_response' + description: Authorization information is missing or invalid. + summary: Attach a file to a case + tags: + - cases /api/cases/{caseId}/user_actions: get: deprecated: true @@ -43674,6 +43714,25 @@ components: - $ref: '#/components/schemas/Cases_add_alert_comment_request_properties' - $ref: '#/components/schemas/Cases_add_user_comment_request_properties' title: Add case comment request + Cases_add_case_file_request: + description: >- + Defines the file that will be attached to the case. Optional parameters + will be generated automatically from the file metadata if not defined. + type: object + properties: + file: + description: The file being attached to the case. + format: binary + type: string + filename: + description: >- + The desired name of the file being attached to the case, it can be + different than the name of the file in the filesystem. **This should + not include the file extension.** + type: string + required: + - file + title: Add case file request properties Cases_add_user_comment_request_properties: description: Defines properties for case comment requests when type is user. properties: diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index 557899e322ae7..aa3855807cea2 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -62,6 +62,8 @@ export const CASE_FIND_USER_ACTIONS_URL = `${CASE_USER_ACTIONS_URL}/_find` as co export const CASE_ALERTS_URL = `${CASES_URL}/alerts/{alert_id}` as const; export const CASE_DETAILS_ALERTS_URL = `${CASE_DETAILS_URL}/alerts` as const; +export const CASE_FILES_URL = `${CASE_DETAILS_URL}/files` as const; + /** * Internal routes */ @@ -139,6 +141,7 @@ export const MAX_TEMPLATE_DESCRIPTION_LENGTH = 1000 as const; export const MAX_TEMPLATES_LENGTH = 10 as const; export const MAX_TEMPLATE_TAG_LENGTH = 50 as const; export const MAX_TAGS_PER_TEMPLATE = 10 as const; +export const MAX_FILENAME_LENGTH = 160 as const; /** * Cases features diff --git a/x-pack/plugins/cases/common/schema/index.test.ts b/x-pack/plugins/cases/common/schema/index.test.ts index 64eb2ad393fcb..8a20943831cd0 100644 --- a/x-pack/plugins/cases/common/schema/index.test.ts +++ b/x-pack/plugins/cases/common/schema/index.test.ts @@ -11,6 +11,7 @@ import { limitedArraySchema, limitedNumberSchema, limitedStringSchema, + mimeTypeString, NonEmptyString, paginationSchema, limitedNumberAsIntegerSchema, @@ -321,14 +322,32 @@ describe('schema', () => { }); }); + describe('mimeTypeString', () => { + it('works correctly when the value is an allowed mime type', () => { + expect(PathReporter.report(mimeTypeString.decode('image/jpx'))).toMatchInlineSnapshot(` + Array [ + "No errors!", + ] + `); + }); + + it('fails when the value is not an allowed mime type', () => { + expect(PathReporter.report(mimeTypeString.decode('foo/bar'))).toMatchInlineSnapshot(` + Array [ + "The mime type field value foo/bar is not allowed.", + ] + `); + }); + }); + describe('limitedNumberAsIntegerSchema', () => { it('works correctly the number is safe integer', () => { expect(PathReporter.report(limitedNumberAsIntegerSchema({ fieldName: 'foo' }).decode(1))) .toMatchInlineSnapshot(` - Array [ - "No errors!", - ] - `); + Array [ + "No errors!", + ] + `); }); it('fails when given a number that is lower than the minimum', () => { diff --git a/x-pack/plugins/cases/common/schema/index.ts b/x-pack/plugins/cases/common/schema/index.ts index 0bcbdcfb2c480..1ddd4a161b6e6 100644 --- a/x-pack/plugins/cases/common/schema/index.ts +++ b/x-pack/plugins/cases/common/schema/index.ts @@ -11,6 +11,7 @@ import { either } from 'fp-ts/lib/Either'; import { MAX_DOCS_PER_PAGE } from '../constants'; import type { PartialPaginationType } from './types'; import { PaginationSchemaRt } from './types'; +import { ALLOWED_MIME_TYPES } from '../constants/mime_types'; export interface LimitedSchemaType { fieldName: string; @@ -194,3 +195,17 @@ export const regexStringRt = ({ codec, pattern, message }: RegexStringSchemaType }), rt.identity ); + +export const mimeTypeString = new rt.Type( + 'mimeTypeString', + rt.string.is, + (input, context) => + either.chain(rt.string.validate(input, context), (s) => { + if (!ALLOWED_MIME_TYPES.includes(s)) { + return rt.failure(input, context, `The mime type field value ${s} is not allowed.`); + } + + return rt.success(s); + }), + rt.identity +); diff --git a/x-pack/plugins/cases/common/types/api/attachment/v1.test.ts b/x-pack/plugins/cases/common/types/api/attachment/v1.test.ts index e08cff94717e4..28c0b5d8da789 100644 --- a/x-pack/plugins/cases/common/types/api/attachment/v1.test.ts +++ b/x-pack/plugins/cases/common/types/api/attachment/v1.test.ts @@ -6,7 +6,11 @@ */ import { PathReporter } from 'io-ts/lib/PathReporter'; -import { MAX_BULK_CREATE_ATTACHMENTS, MAX_COMMENT_LENGTH } from '../../../constants'; +import { + MAX_BULK_CREATE_ATTACHMENTS, + MAX_COMMENT_LENGTH, + MAX_FILENAME_LENGTH, +} from '../../../constants'; import { AttachmentType } from '../../domain/attachment/v1'; import { AttachmentPatchRequestRt, @@ -17,6 +21,7 @@ import { BulkGetAttachmentsRequestRt, BulkGetAttachmentsResponseRt, FindAttachmentsQueryParamsRt, + PostFileAttachmentRequestRt, } from './v1'; describe('Attachments', () => { @@ -389,4 +394,49 @@ describe('Attachments', () => { }); }); }); + + describe('PostFileAttachmentRequestRt', () => { + const defaultRequest = { + file: 'Solve this fast!', + filename: 'filename', + }; + + it('has the expected attributes in request', () => { + const query = PostFileAttachmentRequestRt.decode(defaultRequest); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: defaultRequest, + }); + }); + + it('removes foo:bar attributes from request', () => { + const query = PostFileAttachmentRequestRt.decode({ ...defaultRequest, foo: 'bar' }); + + expect(query).toStrictEqual({ + _tag: 'Right', + right: defaultRequest, + }); + }); + + describe('errors', () => { + it('throws an error when the filename is too long', () => { + const longFilename = 'x'.repeat(MAX_FILENAME_LENGTH + 1); + + expect( + PathReporter.report( + PostFileAttachmentRequestRt.decode({ ...defaultRequest, filename: longFilename }) + ) + ).toContain('The length of the filename is too long. The maximum length is 160.'); + }); + + it('throws an error when the filename is too small', () => { + expect( + PathReporter.report( + PostFileAttachmentRequestRt.decode({ ...defaultRequest, filename: '' }) + ) + ).toContain('The filename field cannot be an empty string.'); + }); + }); + }); }); diff --git a/x-pack/plugins/cases/common/types/api/attachment/v1.ts b/x-pack/plugins/cases/common/types/api/attachment/v1.ts index 3d9cb02a5ce55..a6a2c3f97b39e 100644 --- a/x-pack/plugins/cases/common/types/api/attachment/v1.ts +++ b/x-pack/plugins/cases/common/types/api/attachment/v1.ts @@ -12,6 +12,7 @@ import { MAX_COMMENTS_PER_PAGE, MAX_COMMENT_LENGTH, MAX_DELETE_FILES, + MAX_FILENAME_LENGTH, } from '../../../constants'; import { limitedArraySchema, @@ -47,7 +48,23 @@ export const BulkDeleteFileAttachmentsRequestRt = rt.strict({ }), }); +export const PostFileAttachmentRequestRt = rt.intersection([ + rt.strict({ + file: rt.unknown, + }), + rt.exact( + rt.partial({ + filename: limitedStringSchema({ fieldName: 'filename', min: 1, max: MAX_FILENAME_LENGTH }), + }) + ), +]); + export type BulkDeleteFileAttachmentsRequest = rt.TypeOf; +export type PostFileAttachmentRequest = rt.TypeOf; + +/** + * Attachments + */ const BasicAttachmentRequestRt = rt.union([ UserCommentAttachmentPayloadRt, diff --git a/x-pack/plugins/cases/common/types/domain/attachment/v1.ts b/x-pack/plugins/cases/common/types/domain/attachment/v1.ts index df6193bd3ae36..1dbf1129f58c5 100644 --- a/x-pack/plugins/cases/common/types/domain/attachment/v1.ts +++ b/x-pack/plugins/cases/common/types/domain/attachment/v1.ts @@ -6,8 +6,10 @@ */ import * as rt from 'io-ts'; +import { limitedStringSchema, mimeTypeString } from '../../../schema'; import { jsonValueRt } from '../../../api'; import { UserRt } from '../user/v1'; +import { MAX_FILENAME_LENGTH } from '../../../constants'; /** * Files @@ -35,6 +37,11 @@ export const AttachmentAttributesBasicRt = rt.strict({ updated_by: rt.union([UserRt, rt.null]), }); +export const FileAttachmentMetadataPayloadRt = rt.strict({ + mimeType: mimeTypeString, + filename: limitedStringSchema({ fieldName: 'filename', min: 1, max: MAX_FILENAME_LENGTH }), +}); + /** * User comment */ diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 6b84ab8e090fa..aaf7b7afaec24 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -1869,6 +1869,61 @@ } } } + }, + "/api/cases/{caseId}/files": { + "post": { + "summary": "Attach a file to a case", + "operationId": "addCaseFileDefaultSpace", + "description": "Attach a file to a case. You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. The request must include:\n- The `Content-Type: multipart/form-data` HTTP header.\n- The location of the file that is being uploaded.\n", + "tags": [ + "cases" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/case_id" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/add_case_file_request" + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/case_response_properties" + }, + "examples": { + "addCaseFileResponse": { + "$ref": "#/components/examples/add_comment_response" + } + } + } + } + }, + "401": { + "description": "Authorization information is missing or invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/4xx_response" + } + } + } + } + } + } } }, "components": { @@ -4798,6 +4853,25 @@ "example": "create_case" } } + }, + "add_case_file_request": { + "title": "Add case file request properties", + "required": [ + "file" + ], + "description": "Defines the file that will be attached to the case. Optional parameters will be generated automatically from the file metadata if not defined.", + "type": "object", + "properties": { + "file": { + "description": "The file being attached to the case.", + "type": "string", + "format": "binary" + }, + "filename": { + "description": "The desired name of the file being attached to the case, it can be different than the name of the file in the filesystem. **This should not include the file extension.**", + "type": "string" + } + } } }, "examples": { diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index 40c86d41e1b26..5cfcca36f309a 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -1208,6 +1208,41 @@ paths: application/json: schema: $ref: '#/components/schemas/4xx_response' + /api/cases/{caseId}/files: + post: + summary: Attach a file to a case + operationId: addCaseFileDefaultSpace + description: | + Attach a file to a case. You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. The request must include: + - The `Content-Type: multipart/form-data` HTTP header. + - The location of the file that is being uploaded. + tags: + - cases + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/case_id' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/add_case_file_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/case_response_properties' + examples: + addCaseFileResponse: + $ref: '#/components/examples/add_comment_response' + '401': + description: Authorization information is missing or invalid. + content: + application/json: + schema: + $ref: '#/components/schemas/4xx_response' components: parameters: kbn_xsrf: @@ -3331,6 +3366,20 @@ components: - settings - severity example: create_case + add_case_file_request: + title: Add case file request properties + required: + - file + description: Defines the file that will be attached to the case. Optional parameters will be generated automatically from the file metadata if not defined. + type: object + properties: + file: + description: The file being attached to the case. + type: string + format: binary + filename: + description: The desired name of the file being attached to the case, it can be different than the name of the file in the filesystem. **This should not include the file extension.** + type: string examples: create_case_request: summary: Create a security case that uses a Jira connector. diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/add_case_file_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/add_case_file_request.yaml new file mode 100644 index 0000000000000..d114a7ba1be47 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/add_case_file_request.yaml @@ -0,0 +1,13 @@ +title: Add case file request properties +required: + - file +description: Defines the file that will be attached to the case. Optional parameters will be generated automatically from the file metadata if not defined. +type: object +properties: + file: + description: The file being attached to the case. + type: string + format: binary + filename: + description: The desired name of the file being attached to the case, it can be different than the name of the file in the filesystem. **This should not include the file extension.** + type: string diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml index 764de3e6570fb..09280d6f33ff9 100644 --- a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml +++ b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml @@ -14,7 +14,7 @@ tags: servers: - url: / paths: -# Paths in the default space + # Paths in the default space '/api/cases': $ref: 'paths/api@cases.yaml' '/api/cases/_find': @@ -49,41 +49,43 @@ paths: $ref: 'paths/api@cases@{caseid}@user_actions@_find.yaml' '/api/cases/configure/connectors/_find': $ref: paths/api@cases@configure@connectors@_find.yaml + '/api/cases/{caseId}/files': + $ref: 'paths/api@cases@{caseid}@files.yaml' # Paths with space identifiers - # '/s/{spaceId}/api/cases': - # $ref: 'paths/s@{spaceid}@api@cases.yaml' - # '/s/{spaceId}/api/cases/_find': - # $ref: 'paths/s@{spaceid}@api@cases@_find.yaml' - # '/s/{spaceId}/api/cases/alerts/{alertId}': - # $ref: 'paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml' - # '/s/{spaceId}/api/cases/configure': - # $ref: paths/s@{spaceid}@api@cases@configure.yaml - # '/s/{spaceId}/api/cases/configure/{configurationId}': - # $ref: paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml - # '/s/{spaceId}/api/cases/configure/connectors/_find': - # $ref: paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml - # '/s/{spaceId}/api/cases/reporters': - # $ref: 'paths/s@{spaceid}@api@cases@reporters.yaml' - # '/s/{spaceId}/api/cases/status': - # $ref: 'paths/s@{spaceid}@api@cases@status.yaml' - # '/s/{spaceId}/api/cases/tags': - # $ref: 'paths/s@{spaceid}@api@cases@tags.yaml' - # '/s/{spaceId}/api/cases/{caseId}': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}.yaml' - # '/s/{spaceId}/api/cases/{caseId}/alerts': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml' - # '/s/{spaceId}/api/cases/{caseId}/comments': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments.yaml' - # '/s/{spaceId}/api/cases/{caseId}/comments/_find': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@_find.yaml' - # '/s/{spaceId}/api/cases/{caseId}/comments/{commentId}': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml' - # '/s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml' - # '/s/{spaceId}/api/cases/{caseId}/user_actions': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml' - # '/s/{spaceId}/api/cases/{caseId}/user_actions/_find': - # $ref: 'paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml' +# '/s/{spaceId}/api/cases': +# $ref: 'paths/s@{spaceid}@api@cases.yaml' +# '/s/{spaceId}/api/cases/_find': +# $ref: 'paths/s@{spaceid}@api@cases@_find.yaml' +# '/s/{spaceId}/api/cases/alerts/{alertId}': +# $ref: 'paths/s@{spaceid}@api@cases@alerts@{alertid}.yaml' +# '/s/{spaceId}/api/cases/configure': +# $ref: paths/s@{spaceid}@api@cases@configure.yaml +# '/s/{spaceId}/api/cases/configure/{configurationId}': +# $ref: paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml +# '/s/{spaceId}/api/cases/configure/connectors/_find': +# $ref: paths/s@{spaceid}@api@cases@configure@connectors@_find.yaml +# '/s/{spaceId}/api/cases/reporters': +# $ref: 'paths/s@{spaceid}@api@cases@reporters.yaml' +# '/s/{spaceId}/api/cases/status': +# $ref: 'paths/s@{spaceid}@api@cases@status.yaml' +# '/s/{spaceId}/api/cases/tags': +# $ref: 'paths/s@{spaceid}@api@cases@tags.yaml' +# '/s/{spaceId}/api/cases/{caseId}': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}.yaml' +# '/s/{spaceId}/api/cases/{caseId}/alerts': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@alerts.yaml' +# '/s/{spaceId}/api/cases/{caseId}/comments': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments.yaml' +# '/s/{spaceId}/api/cases/{caseId}/comments/_find': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@_find.yaml' +# '/s/{spaceId}/api/cases/{caseId}/comments/{commentId}': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@comments@{commentid}.yaml' +# '/s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@connector@{connectorid}@_push.yaml' +# '/s/{spaceId}/api/cases/{caseId}/user_actions': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@user_actions.yaml' +# '/s/{spaceId}/api/cases/{caseId}/user_actions/_find': +# $ref: 'paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml' # components: # securitySchemes: # basicAuth: diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@files.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@files.yaml new file mode 100644 index 0000000000000..751e61cd75402 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@files.yaml @@ -0,0 +1,40 @@ +post: + summary: Attach a file to a case + operationId: addCaseFileDefaultSpace + description: > + Attach a file to a case. + You must have `all` privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the case you're updating. + The request must include: + + - The `Content-Type: multipart/form-data` HTTP header. + + - The location of the file that is being uploaded. + tags: + - cases + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/case_id.yaml' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '../components/schemas/add_case_file_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../components/schemas/case_response_properties.yaml' + examples: + addCaseFileResponse: + $ref: '../components/examples/add_comment_response.yaml' + '401': + description: Authorization information is missing or invalid. + content: + application/json: + schema: + $ref: '../components/schemas/4xx_response.yaml' diff --git a/x-pack/plugins/cases/server/client/attachments/add_file.test.ts b/x-pack/plugins/cases/server/client/attachments/add_file.test.ts new file mode 100644 index 0000000000000..16f803389f54a --- /dev/null +++ b/x-pack/plugins/cases/server/client/attachments/add_file.test.ts @@ -0,0 +1,129 @@ +/* + * 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 { Readable } from 'stream'; +import type { File } from '@kbn/files-plugin/common'; + +import type { Case } from '../../../common'; +import { MAX_USER_ACTIONS_PER_CASE } from '../../../common/constants'; +import { createUserActionServiceMock } from '../../services/mocks'; +import { createCasesClientMock, createCasesClientMockArgs } from '../mocks'; +import { addFile } from './add_file'; +import { buildAttachmentRequestFromFileJSON } from '../utils'; + +jest.mock('../utils'); + +const buildAttachmentRequestFromFileJSONMock = buildAttachmentRequestFromFileJSON as jest.Mock; + +describe('addFile', () => { + const caseId = 'test-case'; + const file = new Readable(); + file.push('theFile'); + file.push(null); + const owner = 'cases'; + + const clientArgs = createCasesClientMockArgs(); + const casesClient = createCasesClientMock(); + const userActionService = createUserActionServiceMock(); + + clientArgs.services.userActionService = userActionService; + + beforeEach(() => { + jest.clearAllMocks(); + userActionService.getMultipleCasesUserActionsTotal.mockResolvedValue({}); + casesClient.cases.get.mockResolvedValue({ id: caseId, owner } as unknown as Case); + }); + + it('throws an error if the filename is missing', async () => { + await expect( + addFile( + // @ts-expect-error + { caseId, fileRequest: { file, mimeType: 'text/plain' } }, + clientArgs, + casesClient + ) + ).rejects.toThrow(`Invalid value "undefined" supplied to "filename"`); + }); + + it('throws an error if the mimeType is not part of the allowed mime types', async () => { + await expect( + addFile({ caseId, file, filename: 'foobar', mimeType: 'foo/bar' }, clientArgs, casesClient) + ).rejects.toThrow('The mime type field value foo/bar is not allowed.'); + }); + + it(`throws an error when the case user actions become > ${MAX_USER_ACTIONS_PER_CASE}`, async () => { + userActionService.getMultipleCasesUserActionsTotal.mockResolvedValue({ + [caseId]: MAX_USER_ACTIONS_PER_CASE, + }); + + await expect( + addFile({ caseId, file, filename: 'foobar', mimeType: 'text/plain' }, clientArgs, casesClient) + ).rejects.toThrow( + `The case with id ${caseId} has reached the limit of ${MAX_USER_ACTIONS_PER_CASE} user actions.` + ); + }); + + it('calls fileService.delete when an error is thrown after the file was created', async () => { + const id = 'file-id'; + + clientArgs.fileService.create.mockResolvedValue({ + id, + uploadContent: jest.fn(), + toJSON: () => { + throw new Error(); // ensures an error is thrown after file creation + }, + } as unknown as File); + + await expect( + addFile( + { + caseId, + file, + filename: 'foobar', + mimeType: 'text/plain', + }, + clientArgs, + casesClient + ) + ).rejects.toThrowError(); + expect(clientArgs.fileService.delete).toHaveBeenCalledWith({ id }); + }); + + it('calls buildAttachmentRequestFromFileJSON with the correct params', async () => { + const fileMetadata = { + id: 'file-id', + created: 'created', + extension: 'jpg', + mimeType: 'image/jpeg', + name: 'foobar', + }; + + clientArgs.fileService.create.mockResolvedValue({ + id: fileMetadata.id, + uploadContent: jest.fn(), + toJSON: () => fileMetadata, + } as unknown as File); + + buildAttachmentRequestFromFileJSONMock.mockImplementation(() => { + throw new Error(); // ensures the test finishes early + }); + + await expect( + addFile( + { + caseId, + file, + filename: fileMetadata.name, + mimeType: fileMetadata.mimeType, + }, + clientArgs, + casesClient + ) + ).rejects.toThrowError(); + expect(buildAttachmentRequestFromFileJSONMock).toHaveBeenCalledWith({ owner, fileMetadata }); + }); +}); diff --git a/x-pack/plugins/cases/server/client/attachments/add_file.ts b/x-pack/plugins/cases/server/client/attachments/add_file.ts new file mode 100644 index 0000000000000..5f3ee614e99ed --- /dev/null +++ b/x-pack/plugins/cases/server/client/attachments/add_file.ts @@ -0,0 +1,109 @@ +/* + * 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 { SavedObjectsUtils } from '@kbn/core/server'; + +import type { Owner } from '../../../common/constants/types'; +import { FileAttachmentMetadataPayloadRt, type Case } from '../../../common/types/domain'; +import type { CasesClient, CasesClientArgs } from '..'; +import type { AddFileArgs } from './types'; + +import { CaseCommentModel } from '../../common/models'; +import { createCaseError } from '../../common/error'; +import { validateMaxUserActions } from '../../common/validators'; +import { constructFileKindIdByOwner } from '../../../common/files'; +import { Operations } from '../../authorization'; +import { validateRegisteredAttachments } from './validators'; +import { buildAttachmentRequestFromFileJSON } from '../utils'; +import { decodeWithExcessOrThrow } from '../../common/runtime_types'; + +/** + * Create a file attachment to a case. + */ +export const addFile = async ( + addFileArgs: AddFileArgs, + clientArgs: CasesClientArgs, + casesClient: CasesClient +): Promise => { + const { caseId, file, filename, mimeType, $abort } = addFileArgs; + const { + logger, + authorization, + persistableStateAttachmentTypeRegistry, + externalReferenceAttachmentTypeRegistry, + services: { userActionService }, + fileService, + } = clientArgs; + + let createdFile; + + try { + decodeWithExcessOrThrow(FileAttachmentMetadataPayloadRt)({ + filename, + mimeType, + }); + + // This will perform an authorization check to ensure the user has access to the parent case + const theCase = await casesClient.cases.get({ + id: caseId, + includeComments: false, + }); + + const owner = theCase.owner; + + await validateMaxUserActions({ caseId, userActionService, userActionsToAdd: 1 }); + + const savedObjectID = SavedObjectsUtils.generateId(); + + await authorization.ensureAuthorized({ + operation: Operations.createComment, + entities: [{ owner, id: savedObjectID }], + }); + + createdFile = await fileService.create({ + name: filename, + mime: mimeType, + fileKind: constructFileKindIdByOwner(owner as Owner), + meta: { caseIds: [caseId], owner: [owner] }, + }); + + await createdFile.uploadContent(file, $abort); + + const commentReq = buildAttachmentRequestFromFileJSON({ + owner, + fileMetadata: createdFile.toJSON(), + }); + + validateRegisteredAttachments({ + query: commentReq, + persistableStateAttachmentTypeRegistry, + externalReferenceAttachmentTypeRegistry, + }); + + const createdDate = new Date().toISOString(); + + const model = await CaseCommentModel.create(caseId, clientArgs); + + const updatedModel = await model.createComment({ + createdDate, + commentReq, + id: savedObjectID, + }); + + return await updatedModel.encodeWithComments(); + } catch (error) { + if (createdFile?.id) { + await fileService.delete({ id: createdFile.id }); + } + + throw createCaseError({ + message: `Failed while adding a comment to case id: ${caseId} error: ${error}`, + error, + logger, + }); + } +}; diff --git a/x-pack/plugins/cases/server/client/attachments/client.ts b/x-pack/plugins/cases/server/client/attachments/client.ts index 9c455cbe750c3..61586092e69b9 100644 --- a/x-pack/plugins/cases/server/client/attachments/client.ts +++ b/x-pack/plugins/cases/server/client/attachments/client.ts @@ -28,6 +28,7 @@ import type { UpdateArgs, BulkGetArgs, BulkDeleteFileArgs, + AddFileArgs, } from './types'; import { bulkCreate } from './bulk_create'; import { deleteAll, deleteComment } from './delete'; @@ -35,6 +36,7 @@ import { find, get, getAll, getAllAlertsAttachToCase } from './get'; import { bulkGet } from './bulk_get'; import { update } from './update'; import { bulkDeleteFileAttachments } from './bulk_delete'; +import { addFile } from './add_file'; /** * API for interacting with the attachments to a case. @@ -77,6 +79,10 @@ export interface AttachmentsSubClient { * The request must include all fields for the attachment. Even the fields that are not changing. */ update(updateArgs: UpdateArgs): Promise; + /** + * Adds a file attachment to a case. Returns the case with comments. + */ + addFile(params: AddFileArgs): Promise; } /** @@ -102,6 +108,7 @@ export const createAttachmentsSubClient = ( getAll: (params) => getAll(params, clientArgs), get: (params) => get(params, clientArgs), update: (params) => update(params, clientArgs), + addFile: (params: AddFileArgs) => addFile(params, clientArgs, casesClient), }; return Object.freeze(attachmentSubClient); diff --git a/x-pack/plugins/cases/server/client/attachments/types.ts b/x-pack/plugins/cases/server/client/attachments/types.ts index 0dd700a813110..bc9abed0d0da0 100644 --- a/x-pack/plugins/cases/server/client/attachments/types.ts +++ b/x-pack/plugins/cases/server/client/attachments/types.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type { Readable } from 'stream'; +import type { ReplaySubject } from 'rxjs'; import type { BulkCreateAttachmentsRequest, AttachmentPatchRequest, @@ -132,3 +133,36 @@ export interface UpdateArgs { */ updateRequest: AttachmentPatchRequest; } + +export interface HapiReadableStream extends Readable { + hapi: { + filename: string; + headers: Record; + }; +} + +/** + * The arguments needed for attaching a file to a case. + */ +export interface AddFileArgs { + /** + * The case ID that this attachment will be associated with + */ + caseId: string; + /** + * The file to upload + */ + file: Readable; + /** + * The name of the file to upload + */ + filename: string; + /** + * The mime type of the file to upload + */ + mimeType?: string; + /** + * An observable that can be used to abort the upload at any time. + */ + $abort?: ReplaySubject; +} diff --git a/x-pack/plugins/cases/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts index 74d3c3de46fa0..50dca1920b625 100644 --- a/x-pack/plugins/cases/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -91,6 +91,7 @@ const createAttachmentsSubClientMock = (): AttachmentsSubClientMock => { return { bulkGet: jest.fn(), add: jest.fn(), + addFile: jest.fn(), bulkCreate: jest.fn(), delete: jest.fn(), deleteAll: jest.fn(), diff --git a/x-pack/plugins/cases/server/client/utils.test.ts b/x-pack/plugins/cases/server/client/utils.test.ts index 680887b82c653..7b5d692f6aa79 100644 --- a/x-pack/plugins/cases/server/client/utils.test.ts +++ b/x-pack/plugins/cases/server/client/utils.test.ts @@ -14,6 +14,7 @@ import { toElasticsearchQuery, toKqlExpression } from '@kbn/es-query'; import { createSavedObjectsSerializerMock } from './mocks'; import { arraysDifference, + buildAttachmentRequestFromFileJSON, buildFilter, buildRangeFilter, constructQueryOptions, @@ -24,6 +25,7 @@ import { import { CasePersistedSeverity, CasePersistedStatus } from '../common/types/case'; import type { CustomFieldsConfiguration } from '../../common/types/domain'; import { CaseSeverity, CaseStatuses, CustomFieldTypes } from '../../common/types/domain'; +import type { FileJSON } from '@kbn/shared-ux-file-types'; describe('utils', () => { describe('buildFilter', () => { @@ -1576,4 +1578,40 @@ describe('utils', () => { expect(res).toEqual([]); }); }); + + describe('buildAttachmentRequestFromFileJSON', () => { + it('builds attachment request correctly', () => { + expect( + buildAttachmentRequestFromFileJSON({ + owner: 'theOwner', + fileMetadata: { + id: 'file-id', + created: 'created', + extension: 'jpg', + mimeType: 'image/jpeg', + name: 'foobar', + } as FileJSON, + }) + ).toStrictEqual({ + externalReferenceAttachmentTypeId: '.files', + externalReferenceId: 'file-id', + externalReferenceMetadata: { + files: [ + { + created: 'created', + extension: 'jpg', + mimeType: 'image/jpeg', + name: 'foobar', + }, + ], + }, + externalReferenceStorage: { + soType: 'file', + type: 'savedObject', + }, + owner: 'theOwner', + type: 'externalReference', + }); + }); + }); }); diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts index 5f854aa3236fe..6447b53abd00a 100644 --- a/x-pack/plugins/cases/server/client/utils.ts +++ b/x-pack/plugins/cases/server/client/utils.ts @@ -16,6 +16,8 @@ import type { KueryNode } from '@kbn/es-query'; import { nodeBuilder, fromKueryExpression, escapeKuery } from '@kbn/es-query'; import { spaceIdToNamespace } from '@kbn/spaces-plugin/server/lib/utils/namespace'; +import type { FileJSON } from '@kbn/shared-ux-file-types'; +import { FILE_SO_TYPE } from '@kbn/files-plugin/common/constants'; import type { CaseCustomField, CaseSeverity, @@ -28,6 +30,7 @@ import type { import { ActionsAttachmentPayloadRt, AlertAttachmentPayloadRt, + AttachmentType, ExternalReferenceNoSOAttachmentPayloadRt, ExternalReferenceSOAttachmentPayloadRt, ExternalReferenceStorageType, @@ -40,6 +43,7 @@ import type { CasesSearchParams } from './types'; import { decodeWithExcessOrThrow } from '../common/runtime_types'; import { CASE_SAVED_OBJECT, + FILE_ATTACHMENT_TYPE, NO_ASSIGNEES_FILTERING_KEYWORD, OWNER_FIELD, } from '../../common/constants'; @@ -660,3 +664,30 @@ export const transformTemplateCustomFields = ({ }; }); }; + +export const buildAttachmentRequestFromFileJSON = ({ + owner, + fileMetadata, +}: { + owner: string; + fileMetadata: FileJSON; +}): AttachmentRequest => ({ + owner, + type: AttachmentType.externalReference, + externalReferenceId: fileMetadata.id, + externalReferenceStorage: { + type: ExternalReferenceStorageType.savedObject, + soType: FILE_SO_TYPE, + }, + externalReferenceAttachmentTypeId: FILE_ATTACHMENT_TYPE, + externalReferenceMetadata: { + files: [ + { + name: fileMetadata.name, + extension: fileMetadata.extension ?? 'txt', + mimeType: fileMetadata.mimeType ?? 'text/plain', + created: fileMetadata.created, + }, + ], + }, +}); diff --git a/x-pack/plugins/cases/server/routes/api/files/post_file.test.ts b/x-pack/plugins/cases/server/routes/api/files/post_file.test.ts new file mode 100644 index 0000000000000..5fb2eb581320e --- /dev/null +++ b/x-pack/plugins/cases/server/routes/api/files/post_file.test.ts @@ -0,0 +1,115 @@ +/* + * 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 { createCasesClientMock } from '../../../client/mocks'; +import { postFileRoute } from './post_file'; + +describe('getCaseRoute', () => { + const casesClientMock = createCasesClientMock(); + const response = { ok: jest.fn() }; + const context = { cases: { getCasesClient: jest.fn().mockResolvedValue(casesClientMock) } }; + const sub = { unsubscribe: jest.fn() }; + + afterEach(() => jest.clearAllMocks()); + + it('extracts the file metadata from hapi as expected', async () => { + const request = { + body: { file: { hapi: { filename: 'foobar.txt' } } }, + events: { aborted$: { subscribe: jest.fn().mockReturnValue(sub) } }, + params: { case_id: 'bar' }, + }; + + // @ts-ignore + await postFileRoute.handler({ context, request, response }); + + expect(casesClientMock.attachments.addFile).toHaveBeenCalledWith( + expect.objectContaining({ + caseId: 'bar', + file: { + hapi: { + filename: 'foobar.txt', + }, + }, + filename: 'foobar', + mimeType: 'text/plain', + }) + ); + + expect(sub.unsubscribe).toHaveBeenCalled(); + }); + + it('filename in body takes precedence over metadata', async () => { + const request = { + body: { file: { hapi: { filename: 'foobar.txt' } }, filename: 'foo' }, + events: { aborted$: { subscribe: jest.fn().mockReturnValue(sub) } }, + params: { case_id: 'bar' }, + }; + + // @ts-ignore + await postFileRoute.handler({ context, request, response }); + + expect(casesClientMock.attachments.addFile).toHaveBeenCalledWith( + expect.objectContaining({ + caseId: 'bar', + file: { + hapi: { + filename: 'foobar.txt', + }, + }, + filename: 'foo', + mimeType: 'text/plain', + }) + ); + + expect(sub.unsubscribe).toHaveBeenCalled(); + }); + + it('unrecognized mimetype will be sent as undefined', async () => { + const request = { + body: { file: { hapi: { filename: 'foobar.foobar' } } }, + events: { aborted$: { subscribe: jest.fn().mockReturnValue(sub) } }, + params: { case_id: 'bar' }, + }; + + // @ts-ignore + await postFileRoute.handler({ context, request, response }); + + expect(casesClientMock.attachments.addFile).toHaveBeenCalledWith( + expect.objectContaining({ + caseId: 'bar', + file: { + hapi: { + filename: 'foobar.foobar', + }, + }, + }) + ); + + expect(sub.unsubscribe).toHaveBeenCalled(); + }); + + it('missing hapi will not throw an error', async () => { + const request = { + body: { file: {} }, + events: { aborted$: { subscribe: jest.fn().mockReturnValue(sub) } }, + params: { case_id: 'bar' }, + }; + + // @ts-ignore + await postFileRoute.handler({ context, request, response }); + + expect(casesClientMock.attachments.addFile).toHaveBeenCalledWith( + expect.objectContaining({ + caseId: 'bar', + file: {}, + filename: '', + }) + ); + + expect(sub.unsubscribe).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/server/routes/api/files/post_file.ts b/x-pack/plugins/cases/server/routes/api/files/post_file.ts new file mode 100644 index 0000000000000..2d7b5b6318a4b --- /dev/null +++ b/x-pack/plugins/cases/server/routes/api/files/post_file.ts @@ -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 Boom from '@hapi/boom'; +import mime from 'mime-types'; +import { parse } from 'path'; + +import { schema } from '@kbn/config-schema'; +import { ReplaySubject } from 'rxjs'; + +import { AbortedUploadError } from '@kbn/files-plugin/server/file/errors'; +import type { HapiReadableStream } from '../../../client/attachments/types'; +import type { attachmentApiV1 } from '../../../../common/types/api'; + +import { CASE_FILES_URL } from '../../../../common/constants'; +import { createCaseError } from '../../../common/error'; +import { createCasesRoute } from '../create_cases_route'; +import type { caseDomainV1 } from '../../../../common/types/domain'; + +export const postFileRoute = createCasesRoute({ + method: 'post', + path: CASE_FILES_URL, + params: { + params: schema.object({ + case_id: schema.string(), + }), + }, + routerOptions: { + access: 'public', + summary: 'Attach a file to a case', + tags: ['oas-tag:cases'], + body: { + // This is set to 10 GiB as an upper boundary on the size of the HTTP request body. + // The file service will throw 413 errors if the file size is larger than expected. + maxBytes: 10 * 1024 * 1024 * 1024, + output: 'stream', + parse: true, + accepts: 'multipart/form-data', + }, + }, + handler: async ({ context, request, response }) => { + const $abort = new ReplaySubject(); + const sub = request.events.aborted$.subscribe($abort); + + try { + const caseContext = await context.cases; + const casesClient = await caseContext.getCasesClient(); + + const fileRequest = request.body as attachmentApiV1.PostFileAttachmentRequest; + const file = fileRequest.file as HapiReadableStream; + + let filename = fileRequest.filename; + let mimeType; + + if (file.hapi != null) { + const parsedFilename = parse(file.hapi.filename); + + filename ??= parsedFilename.name; + mimeType = mime.lookup(parsedFilename.ext.toLowerCase()) || undefined; + } + + const res: caseDomainV1.Case = await casesClient.attachments.addFile({ + file, + filename: filename ?? '', + mimeType, + caseId: request.params.case_id, + $abort, + }); + + return response.ok({ + body: res, + }); + } catch (error) { + if (error instanceof AbortedUploadError) { + throw new Boom.Boom(error, { + statusCode: 499, + message: error.message, + }); + } + throw createCaseError({ + message: `Failed to attach file to case in route: ${error}`, + error, + }); + } finally { + sub.unsubscribe(); + } + }, +}); diff --git a/x-pack/plugins/cases/server/routes/api/get_external_routes.ts b/x-pack/plugins/cases/server/routes/api/get_external_routes.ts index 4412d0b695079..b68a1c32a93f8 100644 --- a/x-pack/plugins/cases/server/routes/api/get_external_routes.ts +++ b/x-pack/plugins/cases/server/routes/api/get_external_routes.ts @@ -30,6 +30,7 @@ import { patchCaseConfigureRoute } from './configure/patch_configure'; import { postCaseConfigureRoute } from './configure/post_configure'; import { getAllAlertsAttachedToCaseRoute } from './comments/get_alerts'; import { findUserActionsRoute } from './user_actions/find_user_actions'; +import { postFileRoute } from './files/post_file'; export const getExternalRoutes = ({ isServerless }: { isServerless?: boolean }) => [ @@ -58,4 +59,5 @@ export const getExternalRoutes = ({ isServerless }: { isServerless?: boolean }) patchCaseConfigureRoute, postCaseConfigureRoute, getAllAlertsAttachedToCaseRoute, + postFileRoute, ] as CaseRoute[]; diff --git a/x-pack/test/cases_api_integration/common/lib/api/attachments.ts b/x-pack/test/cases_api_integration/common/lib/api/attachments.ts index 9c06f9b3efa08..7147a1fdf03ee 100644 --- a/x-pack/test/cases_api_integration/common/lib/api/attachments.ts +++ b/x-pack/test/cases_api_integration/common/lib/api/attachments.ts @@ -18,6 +18,7 @@ import { BulkCreateAttachmentsRequest, AttachmentPatchRequest, AttachmentsFindResponse, + PostFileAttachmentRequest, } from '@kbn/cases-plugin/common/types/api'; import { Attachments, Attachment } from '@kbn/cases-plugin/common/types/domain'; import { User } from '../authentication/types'; @@ -79,6 +80,33 @@ export const createComment = async ({ return theCase; }; +export const createFileAttachment = async ({ + supertest, + caseId, + params, + auth = { user: superUser, space: null }, + expectedHttpCode = 200, + headers = {}, +}: { + supertest: SuperTest.Agent; + caseId: string; + params: PostFileAttachmentRequest; + auth?: { user: User; space: string | null } | null; + expectedHttpCode?: number; + headers?: Record; +}): Promise => { + const apiCall = supertest.post(`${getSpaceUrlPrefix(auth?.space)}${CASES_URL}/${caseId}/files`); + void setupAuth({ apiCall, headers, auth }); + + const { body: theCase } = await apiCall + .set('kbn-xsrf', 'true') + .set(headers) + .attach('file', Buffer.from(params.file as unknown as string), params.filename) + .expect(expectedHttpCode); + + return theCase; +}; + export const bulkCreateAttachments = async ({ supertest, caseId, diff --git a/x-pack/test/cases_api_integration/common/lib/mock.ts b/x-pack/test/cases_api_integration/common/lib/mock.ts index 969df10a53d1d..2461cded5aeaa 100644 --- a/x-pack/test/cases_api_integration/common/lib/mock.ts +++ b/x-pack/test/cases_api_integration/common/lib/mock.ts @@ -20,7 +20,10 @@ import { PersistableStateAttachmentPayload, Attachment, } from '@kbn/cases-plugin/common/types/domain'; -import type { CasePostRequest } from '@kbn/cases-plugin/common/types/api'; +import type { + CasePostRequest, + PostFileAttachmentRequest, +} from '@kbn/cases-plugin/common/types/api'; import { FILE_ATTACHMENT_TYPE } from '@kbn/cases-plugin/common/constants'; import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain'; import { FILE_SO_TYPE } from '@kbn/files-plugin/common'; @@ -64,6 +67,11 @@ export const postCommentUserReq: UserCommentAttachmentPayload = { owner: 'securitySolutionFixture', }; +export const postFileReq: PostFileAttachmentRequest = { + file: 'This is a file, a buffer will be created from this string.', + filename: 'foobar.txt', +}; + export const postCommentAlertReq: AlertAttachmentPayload = { alertId: 'test-id', index: 'test-index', diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts index fbfa4354683a5..d38af59f57d16 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts @@ -79,6 +79,9 @@ export default ({ getService }: FtrProviderContext): void => { describe('delete_cases', () => { afterEach(async () => { + await deleteAllFiles({ + supertest, + }); await deleteAllCaseItems(es); }); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/files/post_file.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/files/post_file.ts new file mode 100644 index 0000000000000..b5e27bd85e172 --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/files/post_file.ts @@ -0,0 +1,179 @@ +/* + * 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 { ExternalReferenceAttachmentAttributes } from '@kbn/cases-plugin/common/types/domain'; +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import { + globalRead, + noKibanaPrivileges, + obsOnly, + obsOnlyRead, + obsSecRead, + secOnly, + secOnlyRead, + superUser, +} from '../../../../common/lib/authentication/users'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +import { defaultUser, postCaseReq, postFileReq } from '../../../../common/lib/mock'; +import { + createCase, + createFileAttachment, + removeServerGeneratedPropertiesFromSavedObject, + getAuthWithSuperUser, + deleteAllCaseItems, + superUserSpace1Auth, + deleteAllFiles, +} from '../../../../common/lib/api'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + const authSpace1 = getAuthWithSuperUser(); + const caseRequest = { ...postCaseReq, owner: SECURITY_SOLUTION_OWNER }; + + describe('post_file', () => { + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + describe('happy path', () => { + afterEach(async () => { + await deleteAllFiles({ + supertest, + auth: authSpace1, + }); + }); + + it('should post a file in space1', async () => { + const postedCase = await createCase(supertest, caseRequest, 200, authSpace1); + const patchedCase = await createFileAttachment({ + supertest, + caseId: postedCase.id, + params: postFileReq, + auth: authSpace1, + }); + + const attachedFileComment = removeServerGeneratedPropertiesFromSavedObject( + patchedCase.comments![0] + ) as ExternalReferenceAttachmentAttributes; + // @ts-ignore + const fileMetadata = attachedFileComment.externalReferenceMetadata?.files[0]; + + expect(attachedFileComment.owner).to.be('securitySolution'); + expect(attachedFileComment.externalReferenceId).to.be.ok(); // truthy + expect(attachedFileComment.externalReferenceAttachmentTypeId).to.be('.files'); + expect(attachedFileComment.externalReferenceStorage).to.eql({ + soType: 'file', + type: 'savedObject', + }); + expect(fileMetadata.name).to.be('foobar'); + expect(fileMetadata.mimeType).to.be('text/plain'); + expect(fileMetadata.extension).to.be('txt'); + + // updates the case correctly after adding a comment + expect(patchedCase.totalComment).to.eql(patchedCase.comments!.length); + expect(patchedCase.updated_by).to.eql(defaultUser); + }); + }); + + describe('unhappy path', () => { + it('should return a 400 when posting a file without a filename', async () => { + const postedCase = await createCase(supertest, caseRequest, 200, authSpace1); + await createFileAttachment({ + supertest, + caseId: postedCase.id, + params: { ...postFileReq, filename: '' }, + auth: authSpace1, + expectedHttpCode: 400, + }); + }); + + it('should return a 400 when posting a file with a filename that is too long', async () => { + const longFilename = Array(161).fill('a').toString(); + + const postedCase = await createCase(supertest, caseRequest, 200, authSpace1); + await createFileAttachment({ + supertest, + caseId: postedCase.id, + params: { ...postFileReq, filename: longFilename }, + auth: authSpace1, + expectedHttpCode: 400, + }); + }); + + it('should return a 400 when posting a file with an invalid mime type', async () => { + const postedCase = await createCase(supertest, caseRequest, 200, authSpace1); + await createFileAttachment({ + supertest, + caseId: postedCase.id, + params: { ...postFileReq, filename: 'foobar.124zas' }, + auth: authSpace1, + expectedHttpCode: 400, + }); + }); + }); + + describe('rbac', () => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should not post a file when the user does not have permissions for that owner', async () => { + const postedCase = await createCase(supertest, caseRequest, 200, authSpace1); + + await createFileAttachment({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: postFileReq, + auth: { user: obsOnly, space: 'space1' }, + expectedHttpCode: 403, + }); + }); + + for (const user of [globalRead, secOnlyRead, obsOnlyRead, obsSecRead, noKibanaPrivileges]) { + it(`User ${ + user.username + } with role(s) ${user.roles.join()} - should not post a file`, async () => { + const postedCase = await createCase( + supertestWithoutAuth, + caseRequest, + 200, + superUserSpace1Auth + ); + + await createFileAttachment({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: postFileReq, + auth: { user, space: 'space1' }, + expectedHttpCode: 403, + }); + }); + } + + it('should not post a file in a space the user does not have permissions for', async () => { + const postedCase = await createCase(supertestWithoutAuth, caseRequest, 200, { + user: superUser, + space: 'space2', + }); + + await createFileAttachment({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: postFileReq, + auth: { user: secOnly, space: 'space2' }, + expectedHttpCode: 403, + }); + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts index f9360e473080d..6378ce936e9ea 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts @@ -21,6 +21,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./comments/get_all_comments')); loadTestFile(require.resolve('./comments/patch_comment')); loadTestFile(require.resolve('./comments/post_comment')); + loadTestFile(require.resolve('./files/post_file')); loadTestFile(require.resolve('./alerts/get_cases')); loadTestFile(require.resolve('./alerts/get_alerts_attached_to_case')); loadTestFile(require.resolve('./cases/delete_cases')); diff --git a/x-pack/test/cases_api_integration/spaces_only/tests/common/files/post_file.ts b/x-pack/test/cases_api_integration/spaces_only/tests/common/files/post_file.ts new file mode 100644 index 0000000000000..8e624620b0d4e --- /dev/null +++ b/x-pack/test/cases_api_integration/spaces_only/tests/common/files/post_file.ts @@ -0,0 +1,86 @@ +/* + * 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 { ExternalReferenceAttachmentAttributes } from '@kbn/cases-plugin/common/types/domain'; +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +import { nullUser, postCaseReq, postFileReq } from '../../../../common/lib/mock'; +import { + createCase, + createFileAttachment, + removeServerGeneratedPropertiesFromSavedObject, + getAuthWithSuperUser, + deleteAllCaseItems, + deleteAllFiles, +} from '../../../../common/lib/api'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + // const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const es = getService('es'); + const authSpace1 = getAuthWithSuperUser(); + + describe('post_file', () => { + afterEach(async () => { + await deleteAllFiles({ + supertest: supertestWithoutAuth, + auth: authSpace1, + }); + await deleteAllCaseItems(es); + }); + + it('should post a file in space1', async () => { + const postedCase = await createCase( + supertestWithoutAuth, + { ...postCaseReq, owner: SECURITY_SOLUTION_OWNER }, + 200, + authSpace1 + ); + const patchedCase = await createFileAttachment({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: postFileReq, + auth: authSpace1, + }); + + const attachedFileComment = removeServerGeneratedPropertiesFromSavedObject( + patchedCase.comments![0] + ) as ExternalReferenceAttachmentAttributes; + // @ts-ignore + const fileMetadata = attachedFileComment.externalReferenceMetadata?.files[0]; + + expect(attachedFileComment.owner).to.be('securitySolution'); + expect(attachedFileComment.externalReferenceId).to.be.ok(); // truthy + expect(attachedFileComment.externalReferenceAttachmentTypeId).to.be('.files'); + expect(attachedFileComment.externalReferenceStorage).to.eql({ + soType: 'file', + type: 'savedObject', + }); + expect(fileMetadata.name).to.be('foobar'); + expect(fileMetadata.mimeType).to.be('text/plain'); + expect(fileMetadata.extension).to.be('txt'); + + // updates the case correctly after adding a comment + expect(patchedCase.totalComment).to.eql(patchedCase.comments!.length); + expect(patchedCase.updated_by).to.eql(nullUser); + }); + + it('should not post a file on a case in a different space', async () => { + const postedCase = await createCase(supertestWithoutAuth, postCaseReq, 200, authSpace1); + await createFileAttachment({ + supertest: supertestWithoutAuth, + caseId: postedCase.id, + params: postFileReq, + auth: getAuthWithSuperUser('space2'), + expectedHttpCode: 404, + }); + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/spaces_only/tests/common/index.ts b/x-pack/test/cases_api_integration/spaces_only/tests/common/index.ts index 81da0454f48a4..c8de077b16bca 100644 --- a/x-pack/test/cases_api_integration/spaces_only/tests/common/index.ts +++ b/x-pack/test/cases_api_integration/spaces_only/tests/common/index.ts @@ -17,6 +17,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./comments/get_all_comments')); loadTestFile(require.resolve('./comments/patch_comment')); loadTestFile(require.resolve('./comments/post_comment')); + loadTestFile(require.resolve('./files/post_file')); loadTestFile(require.resolve('./cases/delete_cases')); loadTestFile(require.resolve('./cases/find_cases')); loadTestFile(require.resolve('./cases/get_case')); From 8cf1aacbc6d1959b911ab0f8184946ff540a5c67 Mon Sep 17 00:00:00 2001 From: Tre Date: Thu, 14 Nov 2024 17:07:51 +0000 Subject: [PATCH 11/33] [Ownership] Assign test files to Shared UX Team (#199779) ## Summary Assign test files to Shared UX Team Contributes to: https://github.com/elastic/kibana/issues/192979 --------- Co-authored-by: Robert Oskamp Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ff5ec12fef6a5..12c35f6251f6b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2115,19 +2115,44 @@ x-pack/test/profiling_api_integration @elastic/obs-ux-infra_services-team x-pack/plugins/observability_solution/observability_shared/public/components/profiling @elastic/obs-ux-infra_services-team # Shared UX -/test/api_integration/apis/guided_onboarding @elastic/appex-sharedux +/test/functional/page_objects/files_management.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/200017#discussion_r1840477291 +/test/accessibility/apps/home.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/199771/files#r1840077237 +/test/api_integration/apis/home/*.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/199771/files#r1840077065 +/test/functional/apps/home @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/199771/files#r1840075278 +/x-pack/test/plugin_functional/plugins/global_search_test @elastic/appex-sharedux +/test/functional/services/saved_objects_finder.ts @elastic/appex-sharedux +/test/functional/apps/kibana_overview @elastic/appex-sharedux +/x-pack/test/functional_solution_sidenav/config.ts @elastic/appex-sharedux +/x-pack/test/functional_solution_sidenav/ftr_provider_context.ts @elastic/appex-sharedux +/x-pack/test/functional_solution_sidenav/services.ts @elastic/appex-sharedux +/x-pack/test/functional_solution_sidenav/index.ts @elastic/appex-sharedux +/x-pack/test/functional_cloud @elastic/appex-sharedux +/x-pack/test/functional/services/user_menu.js @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/179270 +/x-pack/test/functional/page_objects/reporting_page.ts @elastic/appex-sharedux +/x-pack/test/accessibility/apps/**/config.ts @elastic/appex-sharedux +/x-pack/test/accessibility/apps/**/index.ts @elastic/appex-sharedux +/x-pack/test/upgrade/apps/reporting @elastic/appex-sharedux +/x-pack/test/functional/apps/advanced_settings @elastic/appex-sharedux +/test/functional/services/monaco_editor.ts @elastic/appex-sharedux +/x-pack/test/functional/fixtures/kbn_archiver/global_search @elastic/appex-sharedux +/x-pack/test/plugin_functional/test_suites/global_search @elastic/appex-sharedux /test/plugin_functional/test_suites/shared_ux @elastic/appex-sharedux -/x-pack/test/functional/apps/advanced_settings @elastic/appex-sharedux @elastic/kibana-management -/test/examples/content_management @elastic/appex-sharedux /test/plugin_functional/plugins/kbn_sample_panel_action @elastic/appex-sharedux -/test/functional/apps/kibana_overview @elastic/appex-sharedux /test/plugin_functional/plugins/eui_provider_dev_warning @elastic/appex-sharedux +/test/functional/page_objects/settings_page.ts @elastic/appex-sharedux +/test/functional/apps/sharing/*.ts @elastic/appex-sharedux +/test/functional/apps/kibana_overviews @elastic/appex-sharedux +/test/examples/ui_actions/*.ts @elastic/appex-sharedux +/test/examples/state_sync/*.ts @elastic/appex-sharedux +/test/examples/error_boundary/index.ts @elastic/appex-sharedux +/test/examples/content_management/*.ts @elastic/appex-sharedux +/test/examples/bfetch_explorer/*.ts @elastic/appex-sharedux +/test/api_integration/apis/guided_onboarding @elastic/appex-sharedux /x-pack/test/banners_functional @elastic/appex-sharedux /x-pack/test/custom_branding @elastic/appex-sharedux -/x-pack/test/accessibility/apps/group3/snapshot_and_restore.ts @elastic/appex-sharedux -/x-pack/test/accessibility/apps/group3/tags.ts @elastic/appex-sharedux /x-pack/test/api_integration/apis/content_management @elastic/appex-sharedux -/x-pack/test/functional_solution_sidenav @elastic/appex-sharedux +/x-pack/test/accessibility/apps/group3/tags.ts @elastic/appex-sharedux +/x-pack/test/accessibility/apps/group3/snapshot_and_restore.ts @elastic/appex-sharedux /x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection.ts @elastic/appex-sharedux /x-pack/test_serverless/functional/test_suites/common/spaces/index.ts @elastic/appex-sharedux packages/react @elastic/appex-sharedux From c30c28f317fafb6997bdbbea22f66d85c58b4865 Mon Sep 17 00:00:00 2001 From: Tre Date: Thu, 14 Nov 2024 17:10:51 +0000 Subject: [PATCH 12/33] [Ownership] Assign test files to management team (#200017) ## Summary Assign test files to management team Contributes to: #192979 --- .github/CODEOWNERS | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 12c35f6251f6b..8e8761ce39dcb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1667,18 +1667,51 @@ x-pack/test/api_integration/apis/management/index_management/inference_endpoints /x-pack/test/functional_search/ @elastic/search-kibana # Management Experience - Deployment Management -/x-pack/test/api_integration/services/index_management.ts @elastic/kibana-management -/x-pack/test/functional/apps/license_management @elastic/kibana-management +/x-pack/test/functional/services/ace_editor.js @elastic/kibana-management +/x-pack/test/functional/page_objects/remote_clusters_page.ts @elastic/kibana-management +/x-pack/test/stack_functional_integration/apps/ccs @elastic/kibana-management +/x-pack/test/functional/services/data_stream.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/watcher_page.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/upgrade_assistant_page.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/snapshot_restore_page.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/rollup_page.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/ingest_pipelines_page.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/grok_debugger_page.ts @elastic/kibana-management +/x-pack/test/functional/page_objects/cross_cluster_replication_page.ts @elastic/kibana-management +/x-pack/test/functional/fixtures/ingest_pipeline_example_mapping.csv @elastic/kibana-management +/x-pack/test/functional/apps/snapshot_restore @elastic/kibana-management /x-pack/test/functional/apps/painless_lab @elastic/kibana-management +/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_management.ts @elastic/kibana-management +/x-pack/test/stack_functional_integration/apps/management @elastic/kibana-management +/x-pack/test/functional/page_objects/*_management_page.ts @elastic/kibana-management +/test/functional/services/saved_query_management_component.ts @elastic/kibana-management +/test/functional/services/management @elastic/kibana-management +/x-pack/test/functional/apps/cross_cluster_replication @elastic/kibana-management +/x-pack/test/functional/apps/ingest_pipelines @elastic/kibana-management +/x-pack/test/functional/apps/home @elastic/kibana-management +/x-pack/test/functional/apps/license_management @elastic/kibana-management /x-pack/test/functional/apps/management @elastic/kibana-management +/x-pack/test/functional/apps/remote_clusters @elastic/kibana-management +/x-pack/test/functional/apps/upgrade_assistant @elastic/kibana-management +/x-pack/test/functional/apps/dev_tools @elastic/kibana-management +/test/plugin_functional/test_suites/management @elastic/kibana-management +/x-pack/test/upgrade_assistant_integration @elastic/kibana-management +/test/plugin_functional/plugins/management_test_plugin @elastic/kibana-management +/test/functional/page_objects/management/*.ts @elastic/kibana-management +/test/functional/page_objects/embedded_console.ts @elastic/kibana-management +/test/functional/page_objects/console_page.ts @elastic/kibana-management +/test/functional/firefox/console.config.ts @elastic/kibana-management +/test/functional/apps/management @elastic/kibana-management +/test/functional/apps/saved_objects_management @elastic/kibana-management +/test/functional/apps/console/*.ts @elastic/kibana-management +/test/api_integration/apis/console/*.ts @elastic/kibana-management +/test/accessibility/apps/management.ts @elastic/kibana-management +/test/accessibility/apps/console.ts @elastic/kibana-management /x-pack/test/api_integration/services/index_management.ts @elastic/kibana-management /x-pack/test/functional/services/grok_debugger.js @elastic/kibana-management -/x-pack/test/functional/apps/cross_cluster_replication @elastic/kibana-management /x-pack/test/functional/apps/grok_debugger @elastic/kibana-management /x-pack/test/functional/apps/index_lifecycle_management @elastic/kibana-management /x-pack/test/functional/apps/index_management @elastic/kibana-management -/x-pack/test/functional/apps/ingest_pipelines @elastic/kibana-management -/x-pack/test/functional/apps/upgrade_assistant @elastic/kibana-management /x-pack/test/api_integration/services/ingest_pipelines @elastic/kibana-management /x-pack/test/functional/apps/watcher @elastic/kibana-management /x-pack/test/api_integration/apis/watcher @elastic/kibana-management From 1ad670439f26ef0265466c02264e76c89a22f188 Mon Sep 17 00:00:00 2001 From: Tre Date: Thu, 14 Nov 2024 17:20:03 +0000 Subject: [PATCH 13/33] [Ownership] Assign test files to security-solution team (#200138) ## Summary Assign test files to security-solution team Contributes to: #192979 Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8e8761ce39dcb..77683d6a94916 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1752,9 +1752,12 @@ x-pack/test/api_integration/apis/management/index_management/inference_endpoints #CC# /x-pack/plugins/cross_cluster_replication/ @elastic/kibana-management # Security Solution +/x-pack/test/functional/es_archives/rule_exceptions @elastic/security-solution # Assigned per https://github.com/elastic/kibana/pull/199795/files/ae80bb252bc71f787c122849fcb9b01e386fc5e9#r1840233040 +/x-pack/test/functional_solution_sidenav/tests/security_sidenav.ts @elastic/security-solution +/x-pack/test/common/utils/security_solution/index.ts @elastic/security-solution +/x-pack/test/plugin_functional/plugins/resolver_test @elastic/security-solution /x-pack/test/common/services/security_solution @elastic/security-solution /x-pack/test/api_integration/services/security_solution_*.gen.ts @elastic/security-solution -/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts @elastic/security-solution /x-pack/test/accessibility/apps/group3/security_solution.ts @elastic/security-solution /x-pack/test_serverless/functional/test_suites/security/config.ts @elastic/security-solution @elastic/appex-qa /x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts @elastic/security-solution From 8dafb11009995f637c71b5689cf6a803d2c9a6fd Mon Sep 17 00:00:00 2001 From: Sandra G Date: Thu, 14 Nov 2024 12:21:30 -0500 Subject: [PATCH 14/33] [Obs AI Assistant] fix spelling error (#199867) ## Summary Fixes spelling error from "degradated" to "degraded". --- .../src/chat/simulated_function_calling_callout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx index 26eb589b25dfc..b570a0cae11f0 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx @@ -19,7 +19,7 @@ export function SimulatedFunctionCallingCallout() { {i18n.translate('xpack.aiAssistant.simulatedFunctionCallingCalloutLabel', { defaultMessage: - 'Simulated function calling is enabled. You might see degradated performance.', + 'Simulated function calling is enabled. You might see degraded performance.', })} From 4e852ea041b63e3e3ad918ceee1bc3861dd1e519 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 14 Nov 2024 17:26:24 +0000 Subject: [PATCH 15/33] [Stack Monitoring / Logs] Fix Stack Monitoring logs links (#200043) ## Summary Fixes https://github.com/elastic/kibana/issues/199902#issuecomment-2474040264. This was missed in https://github.com/elastic/kibana/pull/190835 due to Stack Monitoring's lack of type checking in certain files. `infra` no longer exposes `logsLocators`. This uses `getLogsLocatorsFromUrlService`, technically we could go to Discover now knowing that Logs Explorer will be deprecated in the future. But it will make more sense to convert `getLogsLocatorsFromUrlService` over to using the Discover locators in one when that happens. This puts us on the same page as https://github.com/elastic/kibana/pull/190835. This link should now work, and have the correct filters applied. ![Screenshot 2024-11-13 at 16 09 15](https://github.com/user-attachments/assets/e1f8fd18-ac73-4179-af4c-1727a2afeeec) --------- Co-authored-by: mohamedhamed-ahmed --- x-pack/plugins/monitoring/kibana.jsonc | 6 ++-- .../monitoring/public/components/logs/logs.js | 25 ++++++++++---- .../public/components/logs/logs.test.js | 33 +++++++++++++++---- x-pack/plugins/monitoring/public/plugin.ts | 2 ++ x-pack/plugins/monitoring/public/types.ts | 2 ++ x-pack/plugins/monitoring/tsconfig.json | 1 + 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/monitoring/kibana.jsonc b/x-pack/plugins/monitoring/kibana.jsonc index 51272f995b012..5b9b2853357b9 100644 --- a/x-pack/plugins/monitoring/kibana.jsonc +++ b/x-pack/plugins/monitoring/kibana.jsonc @@ -21,7 +21,8 @@ "observability", "observabilityShared", "dataViews", - "unifiedSearch" + "unifiedSearch", + "share" ], "optionalPlugins": [ "infra", @@ -39,7 +40,8 @@ "requiredBundles": [ "kibanaUtils", "alerting", - "kibanaReact" + "kibanaReact", + "logsShared" ] } } \ No newline at end of file diff --git a/x-pack/plugins/monitoring/public/components/logs/logs.js b/x-pack/plugins/monitoring/public/components/logs/logs.js index 3678c4ae9e27f..3da4bc9dc13ff 100644 --- a/x-pack/plugins/monitoring/public/components/logs/logs.js +++ b/x-pack/plugins/monitoring/public/components/logs/logs.js @@ -10,11 +10,12 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { upperFirst } from 'lodash'; import { Legacy } from '../../legacy_shims'; import { EuiBasicTable, EuiTitle, EuiSpacer, EuiText, EuiCallOut, EuiLink } from '@elastic/eui'; -import { INFRA_SOURCE_ID } from '../../../common/constants'; import { formatDateTimeLocal } from '../../../common/formatting'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { Reason } from './reason'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; const getFormattedDateTimeLocal = (timestamp) => { const timezone = Legacy.shims.uiSettings?.get('dateFormat:tz'); @@ -110,7 +111,7 @@ const clusterColumns = [ }, ]; -function getLogsUiLink(clusterUuid, nodeId, indexUuid) { +function getLogsUiLink(clusterUuid, nodeId, indexUuid, sharePlugin) { const params = []; if (clusterUuid) { params.push(`elasticsearch.cluster.uuid:${clusterUuid}`); @@ -122,15 +123,23 @@ function getLogsUiLink(clusterUuid, nodeId, indexUuid) { params.push(`elasticsearch.index.name:${indexUuid}`); } - const base = Legacy.shims.infra.locators.logsLocator.getRedirectUrl({ - logView: { logViewId: INFRA_SOURCE_ID, type: 'log-view-reference' }, - ...(params.length ? { filter: params.join(' and ') } : {}), + const filter = params.join(' and '); + const { logsLocator } = getLogsLocatorsFromUrlService(sharePlugin.url); + + const base = logsLocator.getRedirectUrl({ + filter, }); return base; } -export class Logs extends PureComponent { +export const Logs = (props) => { + const { + services: { share }, + } = useKibana(); + return ; +}; +export class LogsContent extends PureComponent { renderLogs() { const { logs: { enabled, logs }, @@ -165,7 +174,9 @@ export class Logs extends PureComponent { nodeId, clusterUuid, indexUuid, + sharePlugin, } = this.props; + if (!enabled || !show) { return null; } @@ -184,7 +195,7 @@ export class Logs extends PureComponent { defaultMessage="Visit {link} to dive deeper." values={{ link: ( - + {i18n.translate('xpack.monitoring.logs.listing.calloutLinkText', { defaultMessage: 'Logs', })} diff --git a/x-pack/plugins/monitoring/public/components/logs/logs.test.js b/x-pack/plugins/monitoring/public/components/logs/logs.test.js index 23292ff742fcd..1117a484a294c 100644 --- a/x-pack/plugins/monitoring/public/components/logs/logs.test.js +++ b/x-pack/plugins/monitoring/public/components/logs/logs.test.js @@ -7,7 +7,18 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Logs } from './logs'; +import { LogsContent } from './logs'; +import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; + +const sharePlugin = sharePluginMock.createStartContract(); + +jest.mock('@kbn/logs-shared-plugin/common', () => { + return { + getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({ + logsLocator: { getRedirectUrl: jest.fn(() => '') }, + }), + }; +}); jest.mock('../../legacy_shims', () => ({ Legacy: { @@ -123,32 +134,40 @@ const logs = { describe('Logs', () => { it('should render normally', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); it('should render fewer columns for node or index view', () => { - const component = shallow(); + const component = shallow(); expect(component.find('EuiBasicTable').prop('columns')).toMatchSnapshot(); }); it('should render a link to filter by cluster uuid', () => { - const component = shallow(); + const component = shallow( + + ); expect(component.find('EuiCallOut')).toMatchSnapshot(); }); it('should render a link to filter by cluster uuid and node uuid', () => { - const component = shallow(); + const component = shallow( + + ); expect(component.find('EuiCallOut')).toMatchSnapshot(); }); it('should render a link to filter by cluster uuid and index uuid', () => { - const component = shallow(); + const component = shallow( + + ); expect(component.find('EuiCallOut')).toMatchSnapshot(); }); it('should render a reason if the logs are disabled', () => { - const component = shallow(); + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index c6cfbdc48094c..e29faa9ca5d86 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -101,6 +101,7 @@ export class MonitoringPlugin element: params.element, core: coreStart, data: pluginsStart.data, + share: pluginsStart.share, isCloud: Boolean(plugins.cloud?.isCloudEnabled), pluginInitializerContext: this.initializerContext, externalConfig, @@ -124,6 +125,7 @@ export class MonitoringPlugin appMountParameters: deps.appMountParameters, dataViews: deps.dataViews, infra: deps.infra, + share: deps.share, }); const config = Object.fromEntries(externalConfig); diff --git a/x-pack/plugins/monitoring/public/types.ts b/x-pack/plugins/monitoring/public/types.ts index 5e6590968d232..cd5314521f947 100644 --- a/x-pack/plugins/monitoring/public/types.ts +++ b/x-pack/plugins/monitoring/public/types.ts @@ -16,6 +16,7 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; import { FleetStart } from '@kbn/fleet-plugin/public'; import type { InfraClientStartExports } from '@kbn/infra-plugin/public'; +import { SharePluginStart } from '@kbn/share-plugin/public'; export interface MonitoringStartPluginDependencies { navigation: NavigationStart; @@ -26,6 +27,7 @@ export interface MonitoringStartPluginDependencies { dashboard?: DashboardStart; fleet?: FleetStart; infra?: InfraClientStartExports; + share: SharePluginStart; } interface LegacyStartDependencies { diff --git a/x-pack/plugins/monitoring/tsconfig.json b/x-pack/plugins/monitoring/tsconfig.json index 3b78104e65b8c..48b538a00c1eb 100644 --- a/x-pack/plugins/monitoring/tsconfig.json +++ b/x-pack/plugins/monitoring/tsconfig.json @@ -48,6 +48,7 @@ "@kbn/flot-charts", "@kbn/ui-theme", "@kbn/core-elasticsearch-server", + "@kbn/share-plugin", ], "exclude": [ "target/**/*", From 190430b0b2011235963af069a0196cfafb7a5cd5 Mon Sep 17 00:00:00 2001 From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:28:50 +0100 Subject: [PATCH 16/33] [Spaces] Open 'manage roles' link for spaces assign role flyout in new tab (#199506) ## Summary Closes https://github.com/elastic/kibana-team/issues/1281 Modify the "manage role" link in the assign roles to space tab, so it opens the roles screen in a new tab, with an improvement so that on transitioning back to the assign roles space tab the user is presented with an updated list of roles created in the page that was created opened, if any. Caveat: This approach will continually make calls to refresh the role list on every page visibility event that matches the conditions provided until the flyout gets closed. ##### Visuals https://github.com/user-attachments/assets/64cb296d-246d-4033-a655-7b10d0dafab1 ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../space_assign_role_privilege_form.test.tsx | 28 +++++++++++- .../space_assign_role_privilege_form.tsx | 43 ++++++++++++++++--- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx index 1c97b9c4d2bc6..d35bdbee4b8bb 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx @@ -6,7 +6,7 @@ */ import { EuiThemeProvider } from '@elastic/eui'; -import { render, screen, waitFor, within } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import crypto from 'crypto'; import React from 'react'; @@ -142,6 +142,15 @@ describe('PrivilegesRolesForm', () => { jest.clearAllMocks(); }); + it("would open the 'manage roles' link in a new tab", () => { + getRolesSpy.mockResolvedValue([]); + getAllKibanaPrivilegeSpy.mockResolvedValue(createRawKibanaPrivileges(kibanaFeatures)); + + renderPrivilegeRolesForm(); + + expect(screen.getByText('Manage roles')).toHaveAttribute('target', '_blank'); + }); + it('does not display the privilege selection buttons or customization form when no role is selected', async () => { getRolesSpy.mockResolvedValue([]); getAllKibanaPrivilegeSpy.mockResolvedValue(createRawKibanaPrivileges(kibanaFeatures)); @@ -170,6 +179,23 @@ describe('PrivilegesRolesForm', () => { expect(screen.getByTestId('space-assign-role-create-roles-privilege-button')).toBeDisabled(); }); + it('makes a request to refetch available roles if page transitions back from a user interaction page visibility change', () => { + getRolesSpy.mockResolvedValue([]); + getAllKibanaPrivilegeSpy.mockResolvedValue(createRawKibanaPrivileges(kibanaFeatures)); + + renderPrivilegeRolesForm(); + + expect(getRolesSpy).toHaveBeenCalledTimes(1); + + // trigger click on manage roles link, which is perquisite for page visibility handler to trigger role refetch + fireEvent.click(screen.getByText(/manage roles/i)); + + // trigger page visibility change + fireEvent(document, new Event('visibilitychange')); + + expect(getRolesSpy).toHaveBeenCalledTimes(2); + }); + it('renders with the assign roles button disabled when no base privileges or feature privileges are selected', async () => { getRolesSpy.mockResolvedValue([]); getAllKibanaPrivilegeSpy.mockResolvedValue(createRawKibanaPrivileges(kibanaFeatures)); diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx index e0f3e8f3714c6..74f2b2fde4667 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.tsx @@ -23,6 +23,7 @@ import { EuiSpacer, EuiText, EuiTitle, + useGeneratedHtmlId, } from '@elastic/eui'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import type { FC } from 'react'; @@ -81,10 +82,13 @@ export const PrivilegesRolesForm: FC = (props) => { const [selectedRoles, setSelectedRoles] = useState>( createRolesComboBoxOptions(defaultSelected) ); + const manageRoleLinkId = useGeneratedHtmlId(); const isEditOperation = useRef(Boolean(defaultSelected.length)); - useEffect(() => { - async function fetchRequiredData(spaceId: string) { + const userInvokedPageVisibilityChange = useRef(null); + + const fetchRequiredData = useCallback( + async (spaceId: string) => { setFetchingDataDeps(true); const [systemRoles, _kibanaPrivileges] = await invokeClient((clients) => @@ -109,10 +113,28 @@ export const PrivilegesRolesForm: FC = (props) => { ); setKibanaPrivileges(_kibanaPrivileges); - } + }, + [invokeClient] + ); + useEffect(() => { fetchRequiredData(space.id!).finally(() => setFetchingDataDeps(false)); - }, [invokeClient, space.id]); + }, [fetchRequiredData, invokeClient, space.id]); + + useEffect(() => { + async function visibilityChangeHandler() { + // page just transitioned back to visible state from hidden state caused by user interaction + if (userInvokedPageVisibilityChange.current && !document.hidden) { + await fetchRequiredData(space.id!).finally(() => setFetchingDataDeps(false)); + } + } + + document.addEventListener('visibilitychange', visibilityChangeHandler); + + return () => { + document.removeEventListener('visibilitychange', visibilityChangeHandler); + }; + }, [fetchRequiredData, invokeClient, space.id]); const selectedRolesCombinedPrivileges = useMemo(() => { const combinedPrivilege = new Set( @@ -345,12 +367,23 @@ export const PrivilegesRolesForm: FC = (props) => { {!isEditOperation.current && ( ) => { + // leverage event propagation, check if manage role link element was clicked + if ((event.target as HTMLFieldSetElement).id === manageRoleLinkId) { + userInvokedPageVisibilityChange.current = true; + } + }} label={i18n.translate( 'xpack.spaces.management.spaceDetails.roles.selectRolesFormRowLabel', { defaultMessage: 'Select roles' } )} labelAppend={ - + {i18n.translate( 'xpack.spaces.management.spaceDetails.roles.selectRolesFormRowLabelAnchor', { defaultMessage: 'Manage roles' } From f552a06c674ab4d4acf00aed6e1bf74f16c92a81 Mon Sep 17 00:00:00 2001 From: Tre Date: Thu, 14 Nov 2024 17:39:48 +0000 Subject: [PATCH 17/33] [Ownership] Assign test files to fleet team (#200180) ## Summary Assign test files to fleet team Contributes to: #192979 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 77683d6a94916..2b06c284b6a80 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1248,9 +1248,10 @@ x-pack/test_serverless/**/test_suites/observability/ai_assistant @elastic/obs-ai /x-pack/test/accessibility/apps/group3/stack_monitoring.ts @elastic/stack-monitoring # Fleet -/x-pack/test_serverless/api_integration/test_suites/security/fleet @elastic/fleet -/x-pack/test/fleet_packages @elastic/fleet +/x-pack/test/api_integration/services/fleet_and_agents.ts @elastic/fleet /x-pack/test/fleet_api_integration @elastic/fleet +/x-pack/test/fleet_packages @elastic/fleet +/test/api_integration/apis/custom_integration/*.ts @elastic/fleet /x-pack/test/fleet_cypress @elastic/fleet /x-pack/test/fleet_functional @elastic/fleet /src/dev/build/tasks/bundle_fleet_packages.ts @elastic/fleet @elastic/kibana-operations From ed002eca649a50b6cdf5446d2272d152dfb36e15 Mon Sep 17 00:00:00 2001 From: Tre Date: Thu, 14 Nov 2024 17:42:18 +0000 Subject: [PATCH 18/33] [Ownership] Assign test files to visualizations team (#200144) ## Summary Assign test files to visualizations team Contributes to: #192979 --------- Co-authored-by: Stratoula Kalafateli --- .github/CODEOWNERS | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2b06c284b6a80..99bd6d6b0757d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1100,6 +1100,32 @@ src/plugins/discover/public/context_awareness/profile_providers/security @elasti /x-pack/test/screenshot_creation @elastic/platform-docs # Visualizations +/test/functional/apps/getting_started/*.ts @elastic/kibana-visualizations # Assigned per https://github.com/elastic/kibana/pull/199767#discussion_r1840485031 +/x-pack/test/upgrade/apps/graph @elastic/kibana-visualizations +/x-pack/test/functional/page_objects/log_wrapper.ts @elastic/kibana-visualizations # Assigned per https://github.com/elastic/kibana/pull/36437 +/x-pack/test/functional/page_objects/graph_page.ts @elastic/kibana-visualizations +/x-pack/test/functional/apps/managed_content @elastic/kibana-visualizations +/test/functional/services/visualizations @elastic/kibana-visualizations +/test/functional/services/renderable.ts @elastic/kibana-visualizations +/test/functional/page_objects/vega_chart_page.ts @elastic/kibana-visualizations +/test/functional/page_objects/visual_builder_page.ts @elastic/kibana-visualizations +/test/functional/page_objects/visualize_*.ts @elastic/kibana-visualizations +/test/functional/page_objects/timelion_page.ts @elastic/kibana-visualizations +/test/functional/page_objects/time_to_visualize_page.ts @elastic/kibana-visualizations +/test/functional/page_objects/tag_cloud_page.ts @elastic/kibana-visualizations +/x-pack/test/functional/page_objects/lens_page.ts @elastic/kibana-visualizations +/x-pack/test/functional/es_archives/lens @elastic/kibana-visualizations +/x-pack/test/examples/embedded_lens @elastic/kibana-visualizations +/x-pack/test/api_integration/fixtures/kbn_archiver/lens/constant_keyword.json @elastic/kibana-visualizations +/test/plugin_functional/test_suites/custom_visualizations @elastic/kibana-visualizations +/test/plugin_functional/plugins/kbn_tp_custom_visualizations @elastic/kibana-visualizations +/x-pack/test/functional/fixtures/kbn_archiver/visualize @elastic/kibana-visualizations +/x-pack/test/functional/fixtures/kbn_archiver/lens @elastic/kibana-visualizations +/test/functional/page_objects/legacy/data_table_vis.ts @elastic/kibana-visualizations +/test/functional/page_objects/annotation_library_editor_page.ts @elastic/kibana-visualizations # Assigned per git blame +/test/functional/firefox/visualize.config.ts @elastic/kibana-visualizations +/test/examples/expressions_explorer/*.ts @elastic/kibana-visualizations +/test/accessibility/apps/visualize.ts @elastic/kibana-visualizations /x-pack/test/accessibility/apps/group3/graph.ts @elastic/kibana-visualizations /x-pack/test/accessibility/apps/group2/lens.ts @elastic/kibana-visualizations /x-pack/test/functional/apps/visualize @elastic/kibana-visualizations @@ -1113,6 +1139,10 @@ src/plugins/discover/public/context_awareness/profile_providers/security @elasti /x-pack/test_serverless/functional/fixtures/kbn_archiver/lens/ @elastic/kibana-visualizations packages/kbn-monaco/src/esql @elastic/kibana-esql +# ESQL +/test/api_integration/apis/esql/*.ts @elastic/kibana-esql +/test/functional/services/esql.ts @elastic/kibana-esql + # Global Experience ### Global Experience Reporting From 4d1cb0b586c3434d2c968c059d6e23d71207bfef Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:50:37 +0000 Subject: [PATCH 19/33] [obsUX][APM] Migrate APM metrics tests to agnostic deployment tests (#200175) ### Summary Closes https://github.com/elastic/kibana/issues/198976 Part of https://github.com/elastic/kibana/issues/193245 This PR contains the changes to migrate metrics test folder to Deployment-agnostic testing strategy. #### How to test Serverless ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep="APM" ``` It's recommended to be run against [MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki) Stateful ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep="APM" ``` Checks - [ ] (OPTIONAL, only if a test has been unskipped) Run flaky test suite - [x] local run for serverless - [x] local run for stateful - [x] MKI run for serverless --- .../apis/observability/apm/index.ts | 1 + .../apis/observability/apm/metrics/index.ts | 19 + .../apm}/metrics/memory/generate_data.ts | 0 .../metrics/memory/memory_metrics.spec.ts | 15 +- .../apm/metrics/metrics_charts.spec.ts | 461 +++++++++++++++++ .../apm}/metrics/serverless/generate_data.ts | 0 .../serverless_active_instances.spec.ts | 15 +- .../serverless_functions_overview.spec.ts | 16 +- .../serverless_metrics_charts.spec.ts | 331 +++++++++++++ .../serverless/serverless_summary.spec.ts | 110 +++++ .../es_archiver/metrics_8.0.0/mappings.json | 7 +- .../tests/metrics/metrics_charts.spec.ts | 462 ------------------ .../serverless_metrics_charts.spec.ts | 333 ------------- .../serverless/serverless_summary.spec.ts | 111 ----- 14 files changed, 948 insertions(+), 933 deletions(-) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/index.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/metrics/memory/generate_data.ts (100%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/metrics/memory/memory_metrics.spec.ts (85%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/metrics_charts.spec.ts rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/metrics/serverless/generate_data.ts (100%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/metrics/serverless/serverless_active_instances.spec.ts (89%) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/metrics/serverless/serverless_functions_overview.spec.ts (84%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_metrics_charts.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_summary.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts index d115e10f2373a..b46b2c3f10637 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts @@ -23,6 +23,7 @@ export default function apmApiIntegrationTests({ loadTestFile(require.resolve('./correlations')); loadTestFile(require.resolve('./entities')); loadTestFile(require.resolve('./cold_start')); + loadTestFile(require.resolve('./metrics')); loadTestFile(require.resolve('./services')); loadTestFile(require.resolve('./historical_data')); loadTestFile(require.resolve('./observability_overview')); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/index.ts new file mode 100644 index 0000000000000..454e3b16e9aad --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('metrics', () => { + loadTestFile(require.resolve('./metrics_charts.spec.ts')); + loadTestFile(require.resolve('./memory/memory_metrics.spec.ts')); + loadTestFile(require.resolve('./serverless/serverless_active_instances.spec.ts')); + loadTestFile(require.resolve('./serverless/serverless_functions_overview.spec.ts')); + loadTestFile(require.resolve('./serverless/serverless_metrics_charts.spec.ts')); + loadTestFile(require.resolve('./serverless/serverless_summary.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/memory/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/memory/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/metrics/memory/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/memory/generate_data.ts diff --git a/x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/memory/memory_metrics.spec.ts similarity index 85% rename from x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/memory/memory_metrics.spec.ts index e0f8fc1cf28c2..0bcfff6395fef 100644 --- a/x-pack/test/apm_api_integration/tests/metrics/memory/memory_metrics.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/memory/memory_metrics.spec.ts @@ -6,13 +6,13 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { config, generateData } from './generate_data'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const start = new Date('2023-01-01T00:00:00.000Z').getTime(); const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1; @@ -33,9 +33,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - // FLAKY: https://github.com/elastic/kibana/issues/176990 - registry.when('Memory', { config: 'trial', archives: [] }, () => { + describe('Memory', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await generateData({ start, end, apmSynthtraceEsClient }); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/metrics_charts.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/metrics_charts.spec.ts new file mode 100644 index 0000000000000..f801113fdf823 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/metrics_charts.spec.ts @@ -0,0 +1,461 @@ +/* + * 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 { first } from 'lodash'; +import { GenericMetricsChart } from '@kbn/apm-plugin/server/routes/metrics/fetch_and_transform_metrics'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { ARCHIVER_ROUTES } from '../constants/archiver'; +import { SupertestReturnType } from '../../../../services/apm_api'; + +type ChartResponse = SupertestReturnType<'GET /internal/apm/services/{serviceName}/metrics/charts'>; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const esArchiver = getService('esArchiver'); + + describe('Metrics charts when data is loaded', () => { + before(async () => { + await esArchiver.load(ARCHIVER_ROUTES['metrics_8.0.0']); + }); + + after(async () => { + await esArchiver.unload(ARCHIVER_ROUTES['metrics_8.0.0']); + }); + + describe('for opbeans-node', () => { + describe('returns metrics data', () => { + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', + params: { + path: { serviceName: 'opbeans-node' }, + query: { + start: '2020-09-08T14:50:00.000Z', + end: '2020-09-08T14:55:00.000Z', + agentName: 'nodejs', + environment: 'ENVIRONMENT_ALL', + kuery: ``, + }, + }, + }); + }); + + it('contains CPU usage and System memory usage chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.714, + 0.3877, + 0.75, + 0.2543, + ] + `); + }); + }); + + describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.722093920925555, + 0.718173546796348, + ] + `); + }); + }); + }); + }); + + describe('for opbeans-java', () => { + describe('returns metrics data', () => { + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', + params: { + path: { serviceName: 'opbeans-java' }, + query: { + start: '2020-09-08T14:55:30.000Z', + end: '2020-09-08T15:00:00.000Z', + agentName: 'java', + environment: 'ENVIRONMENT_ALL', + kuery: ``, + }, + }, + }); + }); + + it('has correct chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + "Heap Memory", + "Non-Heap Memory", + "Thread Count", + "Garbage collection per minute", + "Garbage collection time spent per minute", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.203, + 0.178777777777778, + 0.01, + 0.009, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.193, + 0.193, + 0.009, + 0.009, + ] + `); + }); + }); + + describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.707924703557837, + 0.705395980841182, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.707924703557837, + 0.707924703557837, + ] + `); + }); + }); + + describe('Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + "Avg. limit", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 222501617.777778, + 374341632, + 1560281088, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 211472896, + 374341632, + 1560281088, + ] + `); + }); + }); + + describe('Non-Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'non_heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 138573397.333333, + 147677639.111111, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 138162752, + 147386368, + ] + `); + }); + }); + + describe('Thread Count', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'thread_count_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. count", + "Max count", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 44.4444444444444, + 45, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 44, + 44, + ] + `); + }); + }); + + describe('Garbage collection per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_rate_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 3, + ] + `); + }); + }); + + describe('Garbage collection time spent per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_time_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 37500, + ] + `); + }); + }); + }); + + // 9223372036854771712 = memory limit for a c-group when no memory limit is specified + it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { + const chartsResponse = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', + params: { + path: { serviceName: 'opbeans-java' }, + query: { + start: '2020-09-08T15:00:30.000Z', + end: '2020-09-08T15:05:00.000Z', + agentName: 'java', + environment: 'ENVIRONMENT_ALL', + kuery: ``, + }, + }, + }); + + const systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.114523896426499, + 0.114002376090415, + ] + `); + + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.11383724014064, + 0.11383724014064, + ] + `); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/generate_data.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/generate_data.ts diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_active_instances.spec.ts similarity index 89% rename from x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_active_instances.spec.ts index 1b15e03c91987..b490482b4dd52 100644 --- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_active_instances.spec.ts @@ -6,15 +6,15 @@ */ import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import expect from '@kbn/expect'; import { sumBy } from 'lodash'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { config, expectedValues, generateData } from './generate_data'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; @@ -36,8 +36,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - // FLAKY: https://github.com/elastic/kibana/issues/177639 - registry.when('Serverless active instances', { config: 'basic', archives: [] }, () => { + describe('Serverless active instances', () => { const { memoryTotal, billedDurationMs, @@ -47,8 +46,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { } = config; const { expectedMemoryUsed } = expectedValues; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await generateData({ start, end, apmSynthtraceEsClient }); }); diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_functions_overview.spec.ts similarity index 84% rename from x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_functions_overview.spec.ts index 94792228a2859..3acd0921d2602 100644 --- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_functions_overview.spec.ts @@ -7,13 +7,13 @@ import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { config, expectedValues, generateData } from './generate_data'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const start = new Date('2021-01-01T00:00:00.000Z').getTime(); const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; @@ -34,8 +34,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - // FLAKY: https://github.com/elastic/kibana/issues/177641 - registry.when('Serverless functions overview', { config: 'basic', archives: [] }, () => { + describe('Serverless functions overview', () => { const { memoryTotal, billedDurationMs, @@ -45,7 +44,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { } = config; const { expectedMemoryUsed } = expectedValues; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); await generateData({ start, end, apmSynthtraceEsClient }); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_metrics_charts.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_metrics_charts.spec.ts new file mode 100644 index 0000000000000..7f1e3c2a05004 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_metrics_charts.spec.ts @@ -0,0 +1,331 @@ +/* + * 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 { meanBy, sumBy } from 'lodash'; +import { Coordinate } from '@kbn/apm-plugin/typings/timeseries'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; +import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { generateData, config } from './generate_data'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; + +function isNotNullOrZeroCoordinate(coordinate: Coordinate) { + return coordinate.y !== null && coordinate.y !== 0; +} + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + documentType: ApmDocumentType.TransactionMetric, + rollupInterval: RollupInterval.OneMinute, + bucketSizeInSeconds: 60, + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + describe('Serverless metrics charts', () => { + describe('when data is not loaded', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessMetrics = response.body; + }); + + it('returns empty', () => { + serverlessMetrics.charts.forEach((chart) => { + expect(chart.series).to.be.empty(); + }); + }); + }); + + describe('when data is loaded', () => { + const { + memoryTotal, + memoryFree, + billedDurationMs, + coldStartDurationPython, + transactionDuration, + pythonServerlessFunctionNames, + serverlessId, + } = config; + + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateData({ start, end, apmSynthtraceEsClient }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('Python service', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessMetrics = response.body; + }); + + it('returns all metrics chart', () => { + expect(serverlessMetrics.charts.length).to.be.greaterThan(0); + expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ + 'Cold start duration', + 'Cold starts', + 'Compute usage', + 'Lambda Duration', + 'System memory usage', + ]); + }); + + describe('Avg. Duration', () => { + const transactionDurationInMicroSeconds = transactionDuration * 1000; + [ + { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, + { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const avgDurationMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'avg_duration'; + }); + const series = avgDurationMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + let metricsChart: (typeof serverlessMetrics.charts)[0] | undefined; + + describe('Cold start duration', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_duration'; + }); + }); + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(meanValue).to.equal(coldStartDurationPython * 1000); + }); + }); + + describe('Cold start count', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_count'; + }); + }); + + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal( + numberOfTransactionsCreated * pythonServerlessFunctionNames.length + ); + }); + + it('returns correct sum value', () => { + const sumValue = sumBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(sumValue).to.equal( + numberOfTransactionsCreated * pythonServerlessFunctionNames.length + ); + }); + }); + + describe('memory usage', () => { + const expectedFreeMemory = 1 - memoryFree / memoryTotal; + [ + { title: 'Max', expectedValue: expectedFreeMemory }, + { title: 'Average', expectedValue: expectedFreeMemory }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'memory_usage_chart'; + }); + const series = memoryUsageMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + describe('Compute usage', () => { + const GBSeconds = 1024 * 1024 * 1024 * 1000; + let computeUsageMetric: (typeof serverlessMetrics.charts)[0] | undefined; + before(() => { + computeUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'compute_usage'; + }); + }); + it('returns correct overall value', () => { + const expectedValue = + ((memoryTotal * billedDurationMs) / GBSeconds) * numberOfTransactionsCreated * 2; + expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); + }); + + it('returns correct mean value', () => { + const expectedValue = ((memoryTotal * billedDurationMs) / GBSeconds) * 2; + const meanValue = meanBy( + computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), + 'y' + ); + expect(meanValue).to.equal(expectedValue); + }); + }); + }); + + describe('detailed metrics', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + serverlessMetrics = response.body; + }); + + it('returns all metrics chart', () => { + expect(serverlessMetrics.charts.length).to.be.greaterThan(0); + expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ + 'Cold start duration', + 'Cold starts', + 'Compute usage', + 'Lambda Duration', + 'System memory usage', + ]); + }); + + describe('Avg. Duration', () => { + const transactionDurationInMicroSeconds = transactionDuration * 1000; + [ + { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, + { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const avgDurationMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'avg_duration'; + }); + const series = avgDurationMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + let metricsChart: (typeof serverlessMetrics.charts)[0] | undefined; + + describe('Cold start duration', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_duration'; + }); + }); + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(meanValue).to.equal(coldStartDurationPython * 1000); + }); + }); + + describe('Cold start count', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_count'; + }); + }); + + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(numberOfTransactionsCreated); + }); + + it('returns correct sum value', () => { + const sumValue = sumBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + + describe('memory usage', () => { + const expectedFreeMemory = 1 - memoryFree / memoryTotal; + [ + { title: 'Max', expectedValue: expectedFreeMemory }, + { title: 'Average', expectedValue: expectedFreeMemory }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'memory_usage_chart'; + }); + const series = memoryUsageMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + describe('Compute usage', () => { + const GBSeconds = 1024 * 1024 * 1024 * 1000; + let computeUsageMetric: (typeof serverlessMetrics.charts)[0] | undefined; + before(() => { + computeUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'compute_usage'; + }); + }); + it('returns correct overall value', () => { + const expectedValue = + ((memoryTotal * billedDurationMs) / GBSeconds) * numberOfTransactionsCreated; + expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); + }); + + it('returns correct mean value', () => { + const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; + const meanValue = meanBy( + computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), + 'y' + ); + expect(meanValue).to.equal(expectedValue); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_summary.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_summary.spec.ts new file mode 100644 index 0000000000000..c291ffab47648 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/metrics/serverless/serverless_summary.spec.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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { config, expectedValues, generateData } from './generate_data'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/summary`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + describe('Serverless overview', () => { + describe('when data is not loaded', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessSummary = response.body; + }); + + it('returns empty', () => { + expect(serverlessSummary).to.be.empty(); + }); + }); + + describe('when data is loaded', () => { + const { billedDurationMs, pythonServerlessFunctionNames, faasDuration, serverlessId } = + config; + const { expectedMemoryUsedRate } = expectedValues; + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + await generateData({ start, end, apmSynthtraceEsClient }); + }); + + after(() => apmSynthtraceEsClient.clean()); + + describe('Python service', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessSummary = response.body; + }); + + it('returns correct memory avg', () => { + expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); + }); + it('returns correct serverless function total', () => { + expect(serverlessSummary.serverlessFunctionsTotal).to.eql( + pythonServerlessFunctionNames.length + ); + }); + it('returns correct serverless duration avg', () => { + expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); + }); + it('returns correct billed duration avg', () => { + expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); + }); + }); + + describe('detailed metrics', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + serverlessSummary = response.body; + }); + + it('returns correct memory avg', () => { + expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); + }); + it('returns correct serverless function total', () => { + expect(serverlessSummary.serverlessFunctionsTotal).to.eql(1); + }); + it('returns correct serverless duration avg', () => { + expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); + }); + it('returns correct billed duration avg', () => { + expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0/mappings.json index 5cc5a0032cd31..b1044d198a9a3 100644 --- a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0/mappings.json +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/metrics_8.0.0/mappings.json @@ -4073,10 +4073,6 @@ "index": { "auto_expand_replicas": "0-1", "codec": "best_compression", - "lifecycle": { - "name": "apm-rollover-30-days", - "rollover_alias": "apm-8.0.0-metric" - }, "mapping": { "total_fields": { "limit": "2000" @@ -4085,8 +4081,7 @@ "max_docvalue_fields_search": "200", "number_of_replicas": "0", "number_of_shards": "1", - "priority": "100", - "refresh_interval": "1ms" + "priority": "100" } } } diff --git a/x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts deleted file mode 100644 index 92e7c8a80bcb1..0000000000000 --- a/x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts +++ /dev/null @@ -1,462 +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 expect from '@kbn/expect'; -import { first } from 'lodash'; -import { GenericMetricsChart } from '@kbn/apm-plugin/server/routes/metrics/fetch_and_transform_metrics'; -import { SupertestReturnType } from '../../common/apm_api_supertest'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -type ChartResponse = SupertestReturnType<'GET /internal/apm/services/{serviceName}/metrics/charts'>; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - - registry.when( - 'Metrics charts when data is loaded', - { config: 'basic', archives: ['metrics_8.0.0'] }, - () => { - describe('for opbeans-node', () => { - describe('returns metrics data', () => { - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', - params: { - path: { serviceName: 'opbeans-node' }, - query: { - start: '2020-09-08T14:50:00.000Z', - end: '2020-09-08T14:55:00.000Z', - agentName: 'nodejs', - environment: 'ENVIRONMENT_ALL', - kuery: ``, - }, - }, - }); - }); - - it('contains CPU usage and System memory usage chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'cpu_usage_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.714, - 0.3877, - 0.75, - 0.2543, - ] - `); - }); - }); - - describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) - .toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.722093920925555, - 0.718173546796348, - ] - `); - }); - }); - }); - }); - - describe('for opbeans-java', () => { - describe('returns metrics data', () => { - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', - params: { - path: { serviceName: 'opbeans-java' }, - query: { - start: '2020-09-08T14:55:30.000Z', - end: '2020-09-08T15:00:00.000Z', - agentName: 'java', - environment: 'ENVIRONMENT_ALL', - kuery: ``, - }, - }, - }); - }); - - it('has correct chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - "Heap Memory", - "Non-Heap Memory", - "Thread Count", - "Garbage collection per minute", - "Garbage collection time spent per minute", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'cpu_usage_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.203, - 0.178777777777778, - 0.01, - 0.009, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.193, - 0.193, - 0.009, - 0.009, - ] - `); - }); - }); - - describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) - .toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.707924703557837, - 0.705395980841182, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.707924703557837, - 0.707924703557837, - ] - `); - }); - }); - - describe('Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - "Avg. limit", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 222501617.777778, - 374341632, - 1560281088, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 211472896, - 374341632, - 1560281088, - ] - `); - }); - }); - - describe('Non-Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'non_heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 138573397.333333, - 147677639.111111, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 138162752, - 147386368, - ] - `); - }); - }); - - describe('Thread Count', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'thread_count_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. count", - "Max count", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 44.4444444444444, - 45, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 44, - 44, - ] - `); - }); - }); - - describe('Garbage collection per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_rate_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 3, - ] - `); - }); - }); - - describe('Garbage collection time spent per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_time_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 37500, - ] - `); - }); - }); - }); - - // 9223372036854771712 = memory limit for a c-group when no memory limit is specified - it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { - const chartsResponse = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', - params: { - path: { serviceName: 'opbeans-java' }, - query: { - start: '2020-09-08T15:00:30.000Z', - end: '2020-09-08T15:05:00.000Z', - agentName: 'java', - environment: 'ENVIRONMENT_ALL', - kuery: ``, - }, - }, - }); - - const systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.114523896426499, - 0.114002376090415, - ] - `); - - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.11383724014064, - 0.11383724014064, - ] - `); - }); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts deleted file mode 100644 index be823d8cc9898..0000000000000 --- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts +++ /dev/null @@ -1,333 +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 expect from '@kbn/expect'; -import { meanBy, sumBy } from 'lodash'; -import { Coordinate } from '@kbn/apm-plugin/typings/timeseries'; -import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; -import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { generateData, config } from './generate_data'; - -function isNotNullOrZeroCoordinate(coordinate: Coordinate) { - return coordinate.y !== null && coordinate.y !== 0; -} - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - const numberOfTransactionsCreated = 15; - - async function callApi(serviceName: string, serverlessId?: string) { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', - params: { - path: { serviceName }, - query: { - environment: 'test', - kuery: '', - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - documentType: ApmDocumentType.TransactionMetric, - rollupInterval: RollupInterval.OneMinute, - bucketSizeInSeconds: 60, - ...(serverlessId ? { serverlessId } : {}), - }, - }, - }); - } - - registry.when( - 'Serverless metrics charts when data is not loaded', - { config: 'basic', archives: [] }, - () => { - let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; - before(async () => { - const response = await callApi('lambda-python'); - serverlessMetrics = response.body; - }); - - it('returns empty', () => { - serverlessMetrics.charts.forEach((chart) => { - expect(chart.series).to.be.empty(); - }); - }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177642 - registry.when('Serverless metrics charts', { config: 'basic', archives: [] }, () => { - const { - memoryTotal, - memoryFree, - billedDurationMs, - coldStartDurationPython, - transactionDuration, - pythonServerlessFunctionNames, - serverlessId, - } = config; - - // eslint-disable-next-line mocha/no-sibling-hooks - before(async () => { - await generateData({ start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('Python service', () => { - let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; - before(async () => { - const response = await callApi('lambda-python'); - serverlessMetrics = response.body; - }); - - it('returns all metrics chart', () => { - expect(serverlessMetrics.charts.length).to.be.greaterThan(0); - expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ - 'Cold start duration', - 'Cold starts', - 'Compute usage', - 'Lambda Duration', - 'System memory usage', - ]); - }); - - describe('Avg. Duration', () => { - const transactionDurationInMicroSeconds = transactionDuration * 1000; - [ - { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, - { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const avgDurationMetric = serverlessMetrics.charts.find((chart) => { - return chart.key === 'avg_duration'; - }); - const series = avgDurationMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - let metricsChart: (typeof serverlessMetrics.charts)[0] | undefined; - - describe('Cold start duration', () => { - before(() => { - metricsChart = serverlessMetrics.charts.find((chart) => { - return chart.key === 'cold_start_duration'; - }); - }); - it('returns correct overall value', () => { - expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), - 'y' - ); - expect(meanValue).to.equal(coldStartDurationPython * 1000); - }); - }); - - describe('Cold start count', () => { - before(() => { - metricsChart = serverlessMetrics.charts.find((chart) => { - return chart.key === 'cold_start_count'; - }); - }); - - it('returns correct overall value', () => { - expect(metricsChart?.series[0].overallValue).to.equal( - numberOfTransactionsCreated * pythonServerlessFunctionNames.length - ); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), - 'y' - ); - expect(sumValue).to.equal( - numberOfTransactionsCreated * pythonServerlessFunctionNames.length - ); - }); - }); - - describe('memory usage', () => { - const expectedFreeMemory = 1 - memoryFree / memoryTotal; - [ - { title: 'Max', expectedValue: expectedFreeMemory }, - { title: 'Average', expectedValue: expectedFreeMemory }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { - return chart.key === 'memory_usage_chart'; - }); - const series = memoryUsageMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Compute usage', () => { - const GBSeconds = 1024 * 1024 * 1024 * 1000; - let computeUsageMetric: (typeof serverlessMetrics.charts)[0] | undefined; - before(() => { - computeUsageMetric = serverlessMetrics.charts.find((chart) => { - return chart.key === 'compute_usage'; - }); - }); - it('returns correct overall value', () => { - const expectedValue = - ((memoryTotal * billedDurationMs) / GBSeconds) * numberOfTransactionsCreated * 2; - expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); - }); - - it('returns correct mean value', () => { - const expectedValue = ((memoryTotal * billedDurationMs) / GBSeconds) * 2; - const meanValue = meanBy( - computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(meanValue).to.equal(expectedValue); - }); - }); - }); - - describe('detailed metrics', () => { - let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; - before(async () => { - const response = await callApi( - 'lambda-python', - `${serverlessId}${pythonServerlessFunctionNames[0]}` - ); - serverlessMetrics = response.body; - }); - - it('returns all metrics chart', () => { - expect(serverlessMetrics.charts.length).to.be.greaterThan(0); - expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ - 'Cold start duration', - 'Cold starts', - 'Compute usage', - 'Lambda Duration', - 'System memory usage', - ]); - }); - - describe('Avg. Duration', () => { - const transactionDurationInMicroSeconds = transactionDuration * 1000; - [ - { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, - { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const avgDurationMetric = serverlessMetrics.charts.find((chart) => { - return chart.key === 'avg_duration'; - }); - const series = avgDurationMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - let metricsChart: (typeof serverlessMetrics.charts)[0] | undefined; - - describe('Cold start duration', () => { - before(() => { - metricsChart = serverlessMetrics.charts.find((chart) => { - return chart.key === 'cold_start_duration'; - }); - }); - it('returns correct overall value', () => { - expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), - 'y' - ); - expect(meanValue).to.equal(coldStartDurationPython * 1000); - }); - }); - - describe('Cold start count', () => { - before(() => { - metricsChart = serverlessMetrics.charts.find((chart) => { - return chart.key === 'cold_start_count'; - }); - }); - - it('returns correct overall value', () => { - expect(metricsChart?.series[0].overallValue).to.equal(numberOfTransactionsCreated); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated); - }); - }); - - describe('memory usage', () => { - const expectedFreeMemory = 1 - memoryFree / memoryTotal; - [ - { title: 'Max', expectedValue: expectedFreeMemory }, - { title: 'Average', expectedValue: expectedFreeMemory }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { - return chart.key === 'memory_usage_chart'; - }); - const series = memoryUsageMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Compute usage', () => { - const GBSeconds = 1024 * 1024 * 1024 * 1000; - let computeUsageMetric: (typeof serverlessMetrics.charts)[0] | undefined; - before(() => { - computeUsageMetric = serverlessMetrics.charts.find((chart) => { - return chart.key === 'compute_usage'; - }); - }); - it('returns correct overall value', () => { - const expectedValue = - ((memoryTotal * billedDurationMs) / GBSeconds) * numberOfTransactionsCreated; - expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); - }); - - it('returns correct mean value', () => { - const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; - const meanValue = meanBy( - computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(meanValue).to.equal(expectedValue); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts deleted file mode 100644 index 065597eed1709..0000000000000 --- a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts +++ /dev/null @@ -1,111 +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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { config, expectedValues, generateData } from './generate_data'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const apmSynthtraceEsClient = getService('apmSynthtraceEsClient'); - - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - async function callApi(serviceName: string, serverlessId?: string) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/summary`, - params: { - path: { serviceName }, - query: { - environment: 'test', - kuery: '', - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - ...(serverlessId ? { serverlessId } : {}), - }, - }, - }); - } - - registry.when( - 'Serverless overview when data is not loaded', - { config: 'basic', archives: [] }, - () => { - let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; - before(async () => { - const response = await callApi('lambda-python'); - serverlessSummary = response.body; - }); - - it('returns empty', () => { - expect(serverlessSummary).to.be.empty(); - }); - } - ); - - // FLAKY: https://github.com/elastic/kibana/issues/177650 - registry.when('Serverless overview', { config: 'basic', archives: [] }, () => { - const { billedDurationMs, pythonServerlessFunctionNames, faasDuration, serverlessId } = config; - const { expectedMemoryUsedRate } = expectedValues; - - // eslint-disable-next-line mocha/no-sibling-hooks - before(async () => { - await generateData({ start, end, apmSynthtraceEsClient }); - }); - - after(() => apmSynthtraceEsClient.clean()); - - describe('Python service', () => { - let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; - before(async () => { - const response = await callApi('lambda-python'); - serverlessSummary = response.body; - }); - - it('returns correct memory avg', () => { - expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); - }); - it('returns correct serverless function total', () => { - expect(serverlessSummary.serverlessFunctionsTotal).to.eql( - pythonServerlessFunctionNames.length - ); - }); - it('returns correct serverless duration avg', () => { - expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); - }); - it('returns correct billed duration avg', () => { - expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); - }); - }); - - describe('detailed metrics', () => { - let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; - before(async () => { - const response = await callApi( - 'lambda-python', - `${serverlessId}${pythonServerlessFunctionNames[0]}` - ); - serverlessSummary = response.body; - }); - - it('returns correct memory avg', () => { - expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); - }); - it('returns correct serverless function total', () => { - expect(serverlessSummary.serverlessFunctionsTotal).to.eql(1); - }); - it('returns correct serverless duration avg', () => { - expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); - }); - it('returns correct billed duration avg', () => { - expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); - }); - }); - }); -} From 16127fcc8faf128cfd9d2feffc6086eb6330c11f Mon Sep 17 00:00:00 2001 From: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:58:42 +0000 Subject: [PATCH 20/33] [Index Management] Add index mode field in index template form (#199521) Closes https://github.com/elastic/kibana/issues/198620 ## Summary This PR adds a field for index mode setting in the Logistics step in Index Template form. https://github.com/user-attachments/assets/ee38bdec-66ff-468d-a55e-abf5354c3da2 **How to test:** 1. Go to Index Management -> Index Templates and start creating an index template 2. Verify that the index mode is only enabled if the data stream toggle is on. 3. Verify that typing the `logs-*-*` index pattern sets the index mode to "LogsDB": Screenshot 2024-11-13 at 13 00 10 4. Go to the Settings step and verify that the index mode callout is displayed correctly. 5. Go to Review step and verify that Index mode is displayed correctly in both the summary and the preview request. 6. Save the template and verify that the template details tab correctly displays the index mode. Screenshot 2024-11-13 at 17 22 54 Screenshot 2024-11-13 at 17 22 31 ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --- .../template_create.test.tsx | 67 ++++++++++ .../template_edit.test.tsx | 1 + .../template_form.helpers.ts | 8 ++ .../common/constants/index.ts | 1 + .../common/constants/index_modes.ts | 10 ++ .../common/lib/template_serialization.test.ts | 39 +++++- .../common/lib/template_serialization.ts | 26 +++- .../index_management/common/types/index.ts | 1 + .../common/types/templates.ts | 3 +- .../components/wizard_steps/step_settings.tsx | 49 +++++++- .../wizard_steps/step_settings_container.tsx | 21 +++- .../template_form/steps/step_logistics.tsx | 117 +++++++++++++++--- .../template_form/steps/step_review.tsx | 7 +- .../template_form/template_form.tsx | 8 +- .../template_form/template_form_schemas.tsx | 8 ++ .../application/lib/index_mode_labels.ts | 57 ++++++--- .../data_stream_detail_panel.tsx | 4 +- .../data_stream_table/data_stream_table.tsx | 4 +- .../template_details/tabs/tab_summary.tsx | 6 +- .../routes/api/templates/validate_schemas.ts | 1 + .../test/fixtures/template.ts | 4 + .../index_management/lib/templates.helpers.ts | 1 + .../management/index_management/templates.ts | 7 ++ .../index_management/index_template_wizard.ts | 6 + .../index_management/svl_templates.helpers.ts | 1 + .../index_management/index_templates.ts | 2 + 26 files changed, 403 insertions(+), 56 deletions(-) create mode 100644 x-pack/plugins/index_management/common/constants/index_modes.ts diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index a27fb82fb82b0..7ba6832b8833d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -52,6 +52,15 @@ jest.mock('@elastic/eui', () => { }} /> ), + EuiSuperSelect: (props: any) => ( + { + props.onChange(e.target.value); + }} + /> + ), }; }); @@ -301,6 +310,15 @@ describe('', () => { expect(find('stepTitle').text()).toEqual('Index settings (optional)'); }); + it('should display a warning callout displaying the selected index mode', async () => { + const { exists, find } = testBed; + + expect(exists('indexModeCallout')).toBe(true); + expect(find('indexModeCallout').text()).toContain( + 'The index.mode setting has been set to Standard within template Logistics.' + ); + }); + it('should not allow invalid json', async () => { const { form, actions } = testBed; @@ -426,6 +444,53 @@ describe('', () => { }); }); + describe('logistics (step 1)', () => { + beforeEach(async () => { + await act(async () => { + testBed = await setup(httpSetup); + }); + testBed.component.update(); + }); + + it('setting index pattern to logs-*-* should set the index mode to logsdb', async () => { + const { component, actions } = testBed; + // Logistics + await actions.completeStepOne({ name: 'my_logs_template', indexPatterns: ['logs-*-*'] }); + // Component templates + await actions.completeStepTwo(); + // Index settings + await actions.completeStepThree('{}'); + // Mappings + await actions.completeStepFour(); + // Aliases + await actions.completeStepFive(); + + await act(async () => { + actions.clickNextButton(); + }); + component.update(); + + expect(httpSetup.post).toHaveBeenLastCalledWith( + `${API_BASE_PATH}/index_templates`, + expect.objectContaining({ + body: JSON.stringify({ + name: 'my_logs_template', + indexPatterns: ['logs-*-*'], + allowAutoCreate: 'NO_OVERWRITE', + indexMode: 'logsdb', + dataStream: {}, + _kbnMeta: { + type: 'default', + hasDatastream: false, + isLegacy: false, + }, + template: {}, + }), + }) + ); + }); + }); + describe('review (step 6)', () => { beforeEach(async () => { await act(async () => { @@ -529,6 +594,7 @@ describe('', () => { name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, allowAutoCreate: 'TRUE', + indexMode: 'time_series', }); // Component templates await actions.completeStepTwo('test_component_template_1'); @@ -557,6 +623,7 @@ describe('', () => { name: TEMPLATE_NAME, indexPatterns: DEFAULT_INDEX_PATTERNS, allowAutoCreate: 'TRUE', + indexMode: 'time_series', dataStream: {}, _kbnMeta: { type: 'default', diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index dac7dadfa2557..bbd1d24c7906d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -169,6 +169,7 @@ describe('', () => { indexPatterns: ['myPattern*'], version: 1, allowAutoCreate: 'NO_OVERWRITE', + indexMode: 'standard', dataStream: { hidden: true, anyUnknownKey: 'should_be_kept', diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index 7977c4373d765..7c02608d4e3f7 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -148,6 +148,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { enableDataStream, lifecycle, allowAutoCreate, + indexMode, }: Partial & { enableDataStream?: boolean } = {}) => { const { component, form, find } = testBed; @@ -204,6 +205,10 @@ export const formSetup = async (initTestBed: SetupFunc) => { radioOption.simulate('change', { target: { checked: true } }); component.update(); } + + if (indexMode) { + form.setSelectValue('indexModeField', indexMode); + } }); component.update(); @@ -356,6 +361,7 @@ export type TestSubjects = | 'mappingsEditorFieldEdit' | 'mockCodeEditor' | 'mockComboBox' + | 'mockSuperSelect' | 'nameField' | 'nameField.input' | 'nameParameterInput' @@ -364,6 +370,8 @@ export type TestSubjects = | 'orderField.input' | 'priorityField.input' | 'dataStreamField.input' + | 'indexModeField' + | 'indexModeCallout' | 'dataRetentionToggle.input' | 'allowAutoCreateField.input' | 'pageTitle' diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts index 49e2a7f9505a9..81e50fa8b75be 100644 --- a/x-pack/plugins/index_management/common/constants/index.ts +++ b/x-pack/plugins/index_management/common/constants/index.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; export { BASE_PATH } from './base_path'; export { API_BASE_PATH, INTERNAL_API_BASE_PATH } from './api_base_path'; export { INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS } from './invalid_characters'; +export * from './index_modes'; export * from './index_statuses'; // Since each index can have a max length or 255 characters and the max length of diff --git a/x-pack/plugins/index_management/common/constants/index_modes.ts b/x-pack/plugins/index_management/common/constants/index_modes.ts new file mode 100644 index 0000000000000..65099cf4e4f8b --- /dev/null +++ b/x-pack/plugins/index_management/common/constants/index_modes.ts @@ -0,0 +1,10 @@ +/* + * 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 STANDARD_INDEX_MODE = 'standard'; +export const LOGSDB_INDEX_MODE = 'logsdb'; +export const TIME_SERIES_MODE = 'time_series'; diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.test.ts b/x-pack/plugins/index_management/common/lib/template_serialization.test.ts index 8f9f73c334a9f..cb86de2660fd3 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.test.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.test.ts @@ -6,7 +6,8 @@ */ import { deserializeTemplate, serializeTemplate } from './template_serialization'; -import { TemplateDeserialized, TemplateSerialized } from '../types'; +import { TemplateDeserialized, TemplateSerialized, IndexMode } from '../types'; +import { STANDARD_INDEX_MODE, LOGSDB_INDEX_MODE, TIME_SERIES_MODE } from '../constants'; const defaultSerializedTemplate: TemplateSerialized = { template: {}, @@ -17,6 +18,7 @@ const defaultSerializedTemplate: TemplateSerialized = { const defaultDeserializedTemplate: TemplateDeserialized = { name: 'my_template', indexPatterns: ['test'], + indexMode: STANDARD_INDEX_MODE, _kbnMeta: { type: 'default', hasDatastream: true, @@ -26,12 +28,13 @@ const defaultDeserializedTemplate: TemplateDeserialized = { const allowAutoCreateRadioOptions = ['NO_OVERWRITE', 'TRUE', 'FALSE']; const allowAutoCreateSerializedValues = [undefined, true, false]; +const indexModeValues = [STANDARD_INDEX_MODE, LOGSDB_INDEX_MODE, TIME_SERIES_MODE, undefined]; describe('Template serialization', () => { describe('serialization of allow_auto_create parameter', () => { describe('deserializeTemplate()', () => { allowAutoCreateSerializedValues.forEach((value, index) => { - test(`correctly deserializes ${value} value`, () => { + test(`correctly deserializes ${value} allow_auto_create value`, () => { expect( deserializeTemplate({ ...defaultSerializedTemplate, @@ -41,11 +44,29 @@ describe('Template serialization', () => { ).toHaveProperty('allowAutoCreate', allowAutoCreateRadioOptions[index]); }); }); + + indexModeValues.forEach((value) => { + test(`correctly deserializes ${value} index mode settings value`, () => { + expect( + deserializeTemplate({ + ...defaultSerializedTemplate, + name: 'my_template', + template: { + settings: { + index: { + mode: value, + }, + }, + }, + }) + ).toHaveProperty('indexMode', value ?? STANDARD_INDEX_MODE); + }); + }); }); describe('serializeTemplate()', () => { allowAutoCreateRadioOptions.forEach((option, index) => { - test(`correctly serializes ${option} radio option`, () => { + test(`correctly serializes ${option} allowAutoCreate radio option`, () => { expect( serializeTemplate({ ...defaultDeserializedTemplate, @@ -54,6 +75,18 @@ describe('Template serialization', () => { ).toHaveProperty('allow_auto_create', allowAutoCreateSerializedValues[index]); }); }); + + // Only use the first three values (omit undefined) + indexModeValues.slice(0, 3).forEach((value) => { + test(`correctly serializes ${value} indexMode option`, () => { + expect( + serializeTemplate({ + ...defaultDeserializedTemplate, + indexMode: value as IndexMode, + }) + ).toHaveProperty('template.settings.index.mode', value); + }); + }); }); }); }); diff --git a/x-pack/plugins/index_management/common/lib/template_serialization.ts b/x-pack/plugins/index_management/common/lib/template_serialization.ts index 0ed52e3f04ba0..999023704559c 100644 --- a/x-pack/plugins/index_management/common/lib/template_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/template_serialization.ts @@ -11,9 +11,15 @@ import { TemplateSerialized, TemplateListItem, TemplateType, + IndexMode, } from '../types'; import { deserializeESLifecycle } from './data_stream_utils'; -import { allowAutoCreateRadioValues, allowAutoCreateRadioIds } from '../constants'; +import { + allowAutoCreateRadioValues, + allowAutoCreateRadioIds, + STANDARD_INDEX_MODE, + LOGSDB_INDEX_MODE, +} from '../constants'; const hasEntries = (data: object = {}) => Object.entries(data).length > 0; @@ -26,6 +32,7 @@ export function serializeTemplate(templateDeserialized: TemplateDeserialized): T composedOf, ignoreMissingComponentTemplates, dataStream, + indexMode, _meta, allowAutoCreate, deprecated, @@ -34,7 +41,16 @@ export function serializeTemplate(templateDeserialized: TemplateDeserialized): T return { version, priority, - template, + template: { + ...template, + settings: { + ...template?.settings, + index: { + ...template?.settings?.index, + mode: indexMode, + }, + }, + }, index_patterns: indexPatterns, data_stream: dataStream, composed_of: composedOf, @@ -75,6 +91,11 @@ export function deserializeTemplate( const ilmPolicyName = settings?.index?.lifecycle?.name; + const indexMode = (settings?.index?.mode ?? + (indexPatterns.some((pattern) => pattern === 'logs-*-*') + ? LOGSDB_INDEX_MODE + : STANDARD_INDEX_MODE)) as IndexMode; + const deserializedTemplate: TemplateDeserialized = { name, version, @@ -82,6 +103,7 @@ export function deserializeTemplate( ...(template.lifecycle ? { lifecycle: deserializeESLifecycle(template.lifecycle) } : {}), indexPatterns: indexPatterns.sort(), template, + indexMode, ilmPolicy: ilmPolicyName ? { name: ilmPolicyName } : undefined, composedOf: composedOf ?? [], ignoreMissingComponentTemplates: ignoreMissingComponentTemplates ?? [], diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index ef2e8a389c079..7ec100bc1d366 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -19,6 +19,7 @@ export type { DataStream, DataStreamIndex, DataRetention, + IndexMode, } from './data_streams'; export * from './component_templates'; diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts index b05a29a961a73..ab4614200c0b5 100644 --- a/x-pack/plugins/index_management/common/types/templates.ts +++ b/x-pack/plugins/index_management/common/types/templates.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DataRetention, DataStream } from './data_streams'; +import { DataRetention, DataStream, IndexMode } from './data_streams'; import { IndexSettings } from './indices'; import { Aliases } from './aliases'; import { Mappings } from './mappings'; @@ -51,6 +51,7 @@ export interface TemplateDeserialized { priority?: number; // Composable template only allowAutoCreate: string; order?: number; // Legacy template only + indexMode: IndexMode; ilmPolicy?: { name: string; }; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx index 9b26ed38223c4..38a11f03c7ee6 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings.tsx @@ -16,6 +16,8 @@ import { EuiFormRow, EuiText, EuiCode, + EuiCallOut, + EuiLink, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { CodeEditor } from '@kbn/code-editor'; @@ -23,15 +25,19 @@ import { CodeEditor } from '@kbn/code-editor'; import { Forms } from '../../../../../shared_imports'; import { useJsonStep } from './use_json_step'; import { documentationService } from '../../../mappings_editor/shared_imports'; +import { indexModeLabels } from '../../../../lib/index_mode_labels'; +import { IndexMode } from '../../../../../../common/types'; interface Props { onChange: (content: Forms.Content) => void; esDocsBase: string; defaultValue?: { [key: string]: any }; + indexMode?: IndexMode; } export const StepSettings: React.FunctionComponent = React.memo( - ({ defaultValue = {}, onChange, esDocsBase }) => { + ({ defaultValue = {}, onChange, esDocsBase, indexMode }) => { + const { navigateToStep } = Forms.useFormWizardContext(); const { jsonContent, setJsonContent, error } = useJsonStep({ defaultValue, onChange, @@ -80,6 +86,47 @@ export const StepSettings: React.FunctionComponent = React.memo( + {indexMode && ( + <> + + {i18n.translate( + 'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.indexModeSettingLabel', + { + defaultMessage: 'index.mode', + } + )} + + ), + indexMode: indexModeLabels[indexMode], + logisticsLink: ( + navigateToStep(0)}> + {i18n.translate( + 'xpack.idxMgmt.formWizard.stepSettings.indexModeCallout.logisticsLinkLabel', + { + defaultMessage: 'Logistics', + } + )} + + ), + }} + /> + } + color="warning" + iconType="warning" + data-test-subj="indexModeCallout" + /> + + + + )} + {/* Settings code editor */} TemplateDeserialized; } -export const StepSettingsContainer = React.memo(({ esDocsBase }: Props) => { +export const StepSettingsContainer = React.memo(({ esDocsBase, getTemplateData }: Props) => { const { defaultValue, updateContent } = Forms.useContent( 'settings' ); + const { getData } = Forms.useMultiContentContext(); + + let indexMode; + if (getTemplateData) { + const wizardContent = getData(); + // Build the current template object, providing the wizard content data + const template = getTemplateData(wizardContent); + indexMode = template.indexMode; + } return ( - + ); }); diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx index d73d95600e5b1..9742f2eec525f 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_logistics.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback, Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, @@ -14,6 +14,7 @@ import { EuiSpacer, EuiLink, EuiCode, + EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -34,7 +35,13 @@ import { UnitField, timeUnits } from '../../shared'; import { DataRetention } from '../../../../../common'; import { documentationService } from '../../../services/documentation'; import { schemas, nameConfig, nameConfigWithoutValidations } from '../template_form_schemas'; -import { allowAutoCreateRadios } from '../../../../../common/constants'; +import { + allowAutoCreateRadios, + STANDARD_INDEX_MODE, + TIME_SERIES_MODE, + LOGSDB_INDEX_MODE, +} from '../../../../../common/constants'; +import { indexModeLabels, indexModeDescriptions } from '../../../lib/index_mode_labels'; // Create or Form components with partial props that are common to all instances const UseField = getUseField({ component: Field }); @@ -91,6 +98,54 @@ function getFieldsMeta(esDocsBase: string) { ), testSubject: 'dataStreamField', }, + indexMode: { + title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.indexModeTitle', { + defaultMessage: 'Data stream index mode', + }), + description: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.indexModeDescription', { + defaultMessage: + 'The index.mode setting is used to control settings applied in specific domains like ingestions of time series data or logs.', + }), + options: [ + { + value: STANDARD_INDEX_MODE, + inputDisplay: indexModeLabels[STANDARD_INDEX_MODE], + dropdownDisplay: ( + + {indexModeLabels[STANDARD_INDEX_MODE]} + +

{indexModeDescriptions[STANDARD_INDEX_MODE]}

+
+
+ ), + }, + { + value: TIME_SERIES_MODE, + inputDisplay: indexModeLabels[TIME_SERIES_MODE], + dropdownDisplay: ( + + {indexModeLabels[TIME_SERIES_MODE]} + +

{indexModeDescriptions[TIME_SERIES_MODE]}

+
+
+ ), + }, + { + value: LOGSDB_INDEX_MODE, + inputDisplay: indexModeLabels[LOGSDB_INDEX_MODE], + dropdownDisplay: ( + + {indexModeLabels[LOGSDB_INDEX_MODE]} + +

{indexModeDescriptions[LOGSDB_INDEX_MODE]}

+
+
+ ), + }, + ], + testSubject: 'indexModeField', + }, order: { title: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.orderTitle', { defaultMessage: 'Merge order', @@ -198,21 +253,37 @@ export const StepLogistics: React.FunctionComponent = React.memo( isValid: isFormValid, getErrors: getFormErrors, getFormData, + setFieldValue, } = form; - const [{ addMeta, doCreateDataStream, lifecycle }] = useFormData<{ - addMeta: boolean; - lifecycle: DataRetention; - doCreateDataStream: boolean; - }>({ - form, - watch: [ - 'addMeta', - 'lifecycle.enabled', - 'lifecycle.infiniteDataRetention', - 'doCreateDataStream', - ], - }); + const [{ addMeta, doCreateDataStream, lifecycle, indexPatterns: indexPatternsField }] = + useFormData<{ + addMeta: boolean; + lifecycle: DataRetention; + doCreateDataStream: boolean; + indexPatterns: string[]; + }>({ + form, + watch: [ + 'addMeta', + 'lifecycle.enabled', + 'lifecycle.infiniteDataRetention', + 'doCreateDataStream', + 'indexPatterns', + ], + }); + + useEffect(() => { + if ( + indexPatternsField && + indexPatternsField.length === 1 && + indexPatternsField[0] === 'logs-*-*' && + // Only set index mode if index pattern was changed + defaultValue.indexPatterns !== indexPatternsField + ) { + setFieldValue('indexMode', LOGSDB_INDEX_MODE); + } + }, [defaultValue.indexPatterns, indexPatternsField, setFieldValue]); /** * When the consumer call validate() on this step, we submit the form so it enters the "isSubmitted" state @@ -234,6 +305,7 @@ export const StepLogistics: React.FunctionComponent = React.memo( name, indexPatterns, createDataStream, + indexMode, order, priority, version, @@ -312,6 +384,21 @@ export const StepLogistics: React.FunctionComponent = React.memo( )} + {doCreateDataStream && ( + + + + )} + {/* Since data stream and data retention are settings that are only allowed for non legacy, we only need to check if data stream is set to true to show the data retention. diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx index 9cb5c481b6b50..593655da62fef 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_review.tsx @@ -22,7 +22,7 @@ import { EuiCodeBlock, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { getIndexModeLabel } from '../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../lib/index_mode_labels'; import { allowAutoCreateRadioIds } from '../../../../../common/constants'; import { serializers } from '../../../../shared_imports'; @@ -89,6 +89,7 @@ export const StepReview: React.FunctionComponent = React.memo( const { name, indexPatterns, + indexMode, version, order, template: indexTemplate, @@ -277,9 +278,7 @@ export const StepReview: React.FunctionComponent = React.memo( /> - {getIndexModeLabel( - serializedSettings?.['index.mode'] ?? serializedSettings?.index?.mode - )} + {indexModeLabels[indexMode]} {/* Mappings */} diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 2da3eef609a65..53b53a6ebdeee 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -11,7 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiButton, EuiPageHeader } from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; -import { allowAutoCreateRadioIds } from '../../../../common/constants'; +import { allowAutoCreateRadioIds, STANDARD_INDEX_MODE } from '../../../../common/constants'; import { TemplateDeserialized } from '../../../../common'; import { serializers, Forms, GlobalFlyout } from '../../../shared_imports'; import { @@ -118,6 +118,7 @@ export const TemplateForm = ({ name: '', indexPatterns: [], dataStream: {}, + indexMode: STANDARD_INDEX_MODE, template: {}, _kbnMeta: { type: 'default', @@ -341,7 +342,10 @@ export const TemplateForm = ({ )} - + diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx index 5448c932d65e0..2695da7adb813 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form_schemas.tsx @@ -23,6 +23,7 @@ import { allowAutoCreateRadioIds, INVALID_INDEX_PATTERN_CHARS, INVALID_TEMPLATE_NAME_CHARS, + STANDARD_INDEX_MODE, } from '../../../../common/constants'; const { @@ -150,6 +151,13 @@ export const schemas: Record = { }), defaultValue: false, }, + indexMode: { + type: FIELD_TYPES.SUPER_SELECT, + defaultValue: STANDARD_INDEX_MODE, + label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldIndexModeLabel', { + defaultMessage: 'Index mode', + }), + }, order: { type: FIELD_TYPES.NUMBER, label: i18n.translate('xpack.idxMgmt.templateForm.stepLogistics.fieldOrderLabel', { diff --git a/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts b/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts index 409659b8133c3..dd7a712567e9f 100644 --- a/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts +++ b/x-pack/plugins/index_management/public/application/lib/index_mode_labels.ts @@ -6,24 +6,43 @@ */ import { i18n } from '@kbn/i18n'; +import { + STANDARD_INDEX_MODE, + LOGSDB_INDEX_MODE, + TIME_SERIES_MODE, +} from '../../../common/constants'; -export const getIndexModeLabel = (mode?: string | null) => { - switch (mode) { - case 'standard': - case null: - case undefined: - return i18n.translate('xpack.idxMgmt.indexModeLabels.standardModeLabel', { - defaultMessage: 'Standard', - }); - case 'logsdb': - return i18n.translate('xpack.idxMgmt.indexModeLabels.logsdbModeLabel', { - defaultMessage: 'LogsDB', - }); - case 'time_series': - return i18n.translate('xpack.idxMgmt.indexModeLabels.tsdbModeLabel', { - defaultMessage: 'Time series', - }); - default: - return mode; - } +export const indexModeLabels = { + [STANDARD_INDEX_MODE]: i18n.translate('xpack.idxMgmt.indexModeLabels.standardIndexModeLabel', { + defaultMessage: 'Standard', + }), + [LOGSDB_INDEX_MODE]: i18n.translate('xpack.idxMgmt.indexModeLabels.logsdbIndexModeLabel', { + defaultMessage: 'LogsDB', + }), + [TIME_SERIES_MODE]: i18n.translate('xpack.idxMgmt.indexModeLabels.timeSeriesIndexModeLabel', { + defaultMessage: 'Time series', + }), +}; + +export const indexModeDescriptions = { + [STANDARD_INDEX_MODE]: i18n.translate( + 'xpack.idxMgmt.indexModeDescriptions.standardIndexModeDescription', + { + defaultMessage: + 'Standard indexing with default settings, for data other than logs or metrics', + } + ), + [LOGSDB_INDEX_MODE]: i18n.translate( + 'xpack.idxMgmt.indexModeDescriptions.logsdbIndexModeDescription', + { + defaultMessage: + 'Optimized for storing logs data, with reduced storage and default settings that help reduce the chance of logs being rejected by Elasticsearch', + } + ), + [TIME_SERIES_MODE]: i18n.translate( + 'xpack.idxMgmt.indexModeDescriptions.timeSeriesIndexModeDescription', + { + defaultMessage: 'Optimized for metrics data with reduced storage', + } + ), }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index 5b3bf0920c3b7..d962305a7147c 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -34,7 +34,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { getIndexModeLabel } from '../../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../../lib/index_mode_labels'; import { DiscoverLink } from '../../../../lib/discover_link'; import { getLifecycleValue } from '../../../../lib/data_streams'; import { SectionLoading, reactRouterNavigate } from '../../../../../shared_imports'; @@ -355,7 +355,7 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({ defaultMessage: "The index mode applied to the data stream's backing indices, as defined in its associated index template.", }), - content: getIndexModeLabel(indexMode), + content: indexModeLabels[indexMode], dataTestSubj: 'indexModeDetail', }, { diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index e91fd644f795c..59daae719bf47 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -36,7 +36,7 @@ import { humanizeTimeStamp } from '../humanize_time_stamp'; import { DataStreamsBadges } from '../data_stream_badges'; import { ConditionalWrap } from '../data_stream_detail_panel'; import { isDataStreamFullyManagedByILM } from '../../../../lib/data_streams'; -import { getIndexModeLabel } from '../../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../../lib/index_mode_labels'; import { FilterListButton, Filters } from '../../components'; import { type DataStreamFilterName } from '../data_stream_list'; @@ -192,7 +192,7 @@ export const DataStreamTable: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - render: (indexMode: DataStream['indexMode']) => getIndexModeLabel(indexMode), + render: (indexMode: DataStream['indexMode']) => indexModeLabels[indexMode], }); columns.push({ diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx index ff06a08014f61..2621f3ec483c1 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/tabs/tab_summary.tsx @@ -28,7 +28,7 @@ import { TemplateDeserialized } from '../../../../../../../common'; import { ILM_PAGES_POLICY_EDIT } from '../../../../../constants'; import { useIlmLocator } from '../../../../../services/use_ilm_locator'; import { allowAutoCreateRadioIds } from '../../../../../../../common/constants'; -import { getIndexModeLabel } from '../../../../../lib/index_mode_labels'; +import { indexModeLabels } from '../../../../../lib/index_mode_labels'; interface Props { templateDetails: TemplateDeserialized; @@ -54,11 +54,11 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) composedOf, order, indexPatterns = [], + indexMode, ilmPolicy, _meta, _kbnMeta: { isLegacy, hasDatastream }, allowAutoCreate, - template, } = templateDetails; const numIndexPatterns = indexPatterns.length; @@ -231,7 +231,7 @@ export const TabSummary: React.FunctionComponent = ({ templateDetails }) /> - {getIndexModeLabel(template?.settings?.index?.mode)} + {indexModeLabels[indexMode]} {/* Allow auto create */} diff --git a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts index 97cb0a1c1c69c..cfd9c1d18e610 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts @@ -13,6 +13,7 @@ export const templateSchema = schema.object({ version: schema.maybe(schema.number()), order: schema.maybe(schema.number()), priority: schema.maybe(schema.number()), + indexMode: schema.string(), // Not present for legacy templates allowAutoCreate: schema.maybe(schema.string()), template: schema.maybe( diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts index 54df4410352b6..09895b550dc18 100644 --- a/x-pack/plugins/index_management/test/fixtures/template.ts +++ b/x-pack/plugins/index_management/test/fixtures/template.ts @@ -23,6 +23,7 @@ export const getComposableTemplate = ({ isLegacy = false, type = 'default', allowAutoCreate = 'NO_OVERWRITE', + indexMode = 'standard', composedOf = [], }: Partial< TemplateDeserialized & { @@ -42,6 +43,7 @@ export const getComposableTemplate = ({ version, priority, indexPatterns, + indexMode, allowAutoCreate, template: { aliases, @@ -66,6 +68,7 @@ export const getTemplate = ({ order = getRandomNumber(), indexPatterns = [], template: { settings, aliases, mappings } = {}, + indexMode = 'standard', dataStream, composedOf, ignoreMissingComponentTemplates, @@ -85,6 +88,7 @@ export const getTemplate = ({ version, order, indexPatterns, + indexMode, allowAutoCreate, template: { aliases, diff --git a/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts b/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts index ea1e9a2d83bb1..a342df2d6287e 100644 --- a/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts +++ b/x-pack/test/api_integration/apis/management/index_management/lib/templates.helpers.ts @@ -59,6 +59,7 @@ export function templatesHelpers(getService: FtrProviderContext['getService']) { name, indexPatterns, version: 1, + indexMode: 'standard', template: { ...getTemplateMock(isMappingsSourceFieldEnabled) }, _kbnMeta: { isLegacy, diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.ts b/x-pack/test/api_integration/apis/management/index_management/templates.ts index 1fe7e022bfc9a..066df3120be08 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.ts +++ b/x-pack/test/api_integration/apis/management/index_management/templates.ts @@ -92,6 +92,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'hasSettings', 'hasAliases', 'hasMappings', @@ -115,6 +116,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedLegacyKeys = [ 'name', 'indexPatterns', + 'indexMode', 'hasSettings', 'hasAliases', 'hasMappings', @@ -138,6 +140,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedWithDSLKeys = [ 'name', 'indexPatterns', + 'indexMode', 'lifecycle', 'hasSettings', 'hasAliases', @@ -163,6 +166,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedWithILMKeys = [ 'name', 'indexPatterns', + 'indexMode', 'ilmPolicy', 'hasSettings', 'hasAliases', @@ -190,6 +194,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'template', 'composedOf', 'ignoreMissingComponentTemplates', @@ -213,6 +218,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'template', 'order', 'version', @@ -375,6 +381,7 @@ export default function ({ getService }: FtrProviderContext) { _kbnMeta: { hasDatastream: false, type: 'default' }, name: templateName, indexPatterns: [getRandomString()], + indexMode: 'standard', template: {}, deprecated: true, allowAutoCreate: 'TRUE', diff --git a/x-pack/test/functional/apps/index_management/index_template_wizard.ts b/x-pack/test/functional/apps/index_management/index_template_wizard.ts index 581a0b2761644..d932b96d4f6a1 100644 --- a/x-pack/test/functional/apps/index_management/index_template_wizard.ts +++ b/x-pack/test/functional/apps/index_management/index_template_wizard.ts @@ -67,6 +67,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const stepTitle = await testSubjects.getVisibleText('stepTitle'); expect(stepTitle).to.be('Index settings (optional)'); + // Verify that index mode callout is displayed + const indexModeCalloutText = await testSubjects.getVisibleText('indexModeCallout'); + expect(indexModeCalloutText).to.be( + 'The index.mode setting has been set to Standard within template Logistics. Any changes to index.mode set on this page will be overwritten by the Logistics selection.' + ); + // Click Next button await pageObjects.indexManagement.clickNextButton(); }); diff --git a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts index 0f6006fd91470..8c96011936743 100644 --- a/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts +++ b/x-pack/test_serverless/api_integration/services/index_management/svl_templates.helpers.ts @@ -56,6 +56,7 @@ export function SvlTemplatesHelpers({ getService }: FtrProviderContext) { const baseTemplate: TemplateDeserialized = { name, indexPatterns, + indexMode: 'standard', version: 1, template: { ...getTemplateMock(isMappingsSourceFieldEnabled) }, _kbnMeta: { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts index 1e0dff8bef632..1190a642a3518 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/index_templates.ts @@ -90,6 +90,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'hasSettings', 'hasAliases', 'hasMappings', @@ -114,6 +115,7 @@ export default function ({ getService }: FtrProviderContext) { const expectedKeys = [ 'name', 'indexPatterns', + 'indexMode', 'template', '_kbnMeta', 'allowAutoCreate', From ab965f75a6bdfb75e2a29454d8f3830d0cf4cf18 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Thu, 14 Nov 2024 11:07:26 -0800 Subject: [PATCH 21/33] [Cloud Security] Alerts Datagrids for Contextual Flyout (#199573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR is for Alerts Datagrid component in Contextual Flyout This PR is for Alerts Datagrid in Contextual Flyout for User name and Host name Screenshot 2024-11-14 at 9 08 26โ€ฏAM --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/utils/helpers.test.ts | 75 +++++ .../common/utils/helpers.ts | 57 ++++ .../hooks/use_misconfiguration_findings.ts | 5 +- .../components/alerts/alerts_preview.test.tsx | 25 +- .../components/alerts/alerts_preview.tsx | 145 +++++++++- .../alerts_findings_details_table.tsx | 265 ++++++++++++++++++ .../csp_details/insights_tab_csp.tsx | 29 +- .../components/entity_insight.tsx | 20 +- .../misconfiguration_preview.tsx | 10 +- .../vulnerabilities_preview.tsx | 4 + ...vigate_to_alerts_page_with_filters.test.ts | 2 + ...se_navigate_to_alerts_page_with_filters.ts | 3 +- .../host_details_left/index.tsx | 13 +- .../entity_details/host_right/index.tsx | 20 ++ .../left_panel/left_panel_header.tsx | 1 + .../user_details_left/index.tsx | 12 +- .../entity_details/user_details_left/tabs.tsx | 14 +- .../entity_details/user_right/index.tsx | 23 +- 18 files changed, 693 insertions(+), 30 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts index 0248cdf9b6e36..04cb76f4441c5 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.test.ts @@ -10,6 +10,7 @@ import { defaultErrorMessage, buildMutedRulesFilter, buildEntityFlyoutPreviewQuery, + buildEntityAlertsQuery, } from './helpers'; const fallbackMessage = 'thisIsAFallBackMessage'; @@ -182,4 +183,78 @@ describe('test helper methods', () => { expect(buildEntityFlyoutPreviewQuery(field)).toEqual(expectedQuery); }); }); + + describe('buildEntityAlertsQuery', () => { + const getExpectedAlertsQuery = (size?: number) => { + return { + size: size || 0, + _source: false, + fields: [ + '_id', + '_index', + 'kibana.alert.rule.uuid', + 'kibana.alert.severity', + 'kibana.alert.rule.name', + 'kibana.alert.workflow_status', + ], + query: { + bool: { + filter: [ + { + bool: { + must: [], + filter: [ + { + match_phrase: { + 'host.name': { + query: 'exampleHost', + }, + }, + }, + ], + should: [], + must_not: [], + }, + }, + { + range: { + '@timestamp': { + gte: 'Today', + lte: 'Tomorrow', + }, + }, + }, + { + terms: { + 'kibana.alert.workflow_status': ['open', 'acknowledged'], + }, + }, + ], + }, + }, + }; + }; + + it('should return the correct query when given all params', () => { + const field = 'host.name'; + const query = 'exampleHost'; + const to = 'Tomorrow'; + const from = 'Today'; + const size = 100; + + expect(buildEntityAlertsQuery(field, to, from, query, size)).toEqual( + getExpectedAlertsQuery(size) + ); + }); + + it('should return the correct query when not given size', () => { + const field = 'host.name'; + const query = 'exampleHost'; + const to = 'Tomorrow'; + const from = 'Today'; + const size = undefined; + + expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size)); + }); + }); }); diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts index 7039c99af6d53..bd531fa63804f 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; + import { i18n } from '@kbn/i18n'; import type { CspBenchmarkRulesStates } from '../schema/rules/latest'; @@ -62,3 +63,59 @@ export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string }, }; }; + +export const buildEntityAlertsQuery = ( + field: string, + to: string, + from: string, + queryValue?: string, + size?: number +) => { + return { + size: size || 0, + _source: false, + fields: [ + '_id', + '_index', + 'kibana.alert.rule.uuid', + 'kibana.alert.severity', + 'kibana.alert.rule.name', + 'kibana.alert.workflow_status', + ], + query: { + bool: { + filter: [ + { + bool: { + must: [], + filter: [ + { + match_phrase: { + [field]: { + query: queryValue, + }, + }, + }, + ], + should: [], + must_not: [], + }, + }, + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, + }, + { + terms: { + 'kibana.alert.workflow_status': ['open', 'acknowledged'], + }, + }, + ], + }, + }, + }; +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts index 40880b132537d..9bbaedf587dde 100644 --- a/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts +++ b/x-pack/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts @@ -40,10 +40,11 @@ export const useMisconfigurationFindings = (options: UseCspOptions) => { params: buildMisconfigurationsFindingsQuery(options, rulesStates!), }) ); - if (!aggregations) throw new Error('expected aggregations to be defined'); + if (!aggregations && options.ignore_unavailable === false) + throw new Error('expected aggregations to be defined'); return { - count: getMisconfigurationAggregationCount(aggregations.count.buckets), + count: getMisconfigurationAggregationCount(aggregations?.count.buckets), rows: hits.hits.map((finding) => ({ result: finding._source?.result, rule: finding?._source?.rule, diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx index e0199ab40168d..fff9450b6a1cb 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.test.tsx @@ -11,6 +11,9 @@ import { AlertsPreview } from './alerts_preview'; import { TestProviders } from '../../../common/mock/test_providers'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import type { ParsedAlertsData } from '../../../overview/components/detection_response/alerts_by_status/types'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; +import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; const mockAlertsData: ParsedAlertsData = { open: { @@ -29,9 +32,10 @@ const mockAlertsData: ParsedAlertsData = { }, }; -jest.mock( - '../../../detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data' -); +// Mock hooks +jest.mock('@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'); +jest.mock('@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'); +jest.mock('../../../entity_analytics/api/hooks/use_risk_score'); jest.mock('@kbn/expandable-flyout'); describe('AlertsPreview', () => { @@ -39,6 +43,13 @@ describe('AlertsPreview', () => { beforeEach(() => { (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); + (useVulnerabilitiesPreview as jest.Mock).mockReturnValue({ + data: { count: { CRITICAL: 0, HIGH: 1, MEDIUM: 1, LOW: 0, UNKNOWN: 0 } }, + }); + (useRiskScore as jest.Mock).mockReturnValue({ data: [{ host: { risk: 75 } }] }); + (useMisconfigurationPreview as jest.Mock).mockReturnValue({ + data: { count: { passed: 1, failed: 1 } }, + }); }); afterEach(() => { jest.clearAllMocks(); @@ -47,17 +58,17 @@ describe('AlertsPreview', () => { it('renders', () => { const { getByTestId } = render( - + ); - expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleText')).toBeInTheDocument(); + expect(getByTestId('securitySolutionFlyoutInsightsAlertsTitleLink')).toBeInTheDocument(); }); it('renders correct alerts number', () => { const { getByTestId } = render( - + ); @@ -67,7 +78,7 @@ describe('AlertsPreview', () => { it('should render the correct number of distribution bar section based on the number of severities', () => { const { queryAllByTestId } = render( - + ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx index 3f9a0115d9ed1..c832f12c93f78 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/alerts/alerts_preview.tsx @@ -5,19 +5,40 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback, useMemo } from 'react'; import { capitalize } from 'lodash'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; -import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common'; -import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel'; -import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers'; +import { + buildEntityFlyoutPreviewQuery, + getAbbreviatedNumber, +} from '@kbn/cloud-security-posture-common'; +import { hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; +import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import type { AlertsByStatus, ParsedAlertsData, } from '../../../overview/components/detection_response/alerts_by_status/types'; +import { ExpandablePanel } from '../../../flyout/shared/components/expandable_panel'; +import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers'; +import type { HostRiskScore, UserRiskScore } from '../../../../common/search_strategy'; +import { + buildHostNamesFilter, + buildUserNamesFilter, + RiskScoreEntity, +} from '../../../../common/search_strategy'; +import { useRiskScore } from '../../../entity_analytics/api/hooks/use_risk_score'; +import { FIRST_RECORD_PAGINATION } from '../../../entity_analytics/common'; +import { HostDetailsPanelKey } from '../../../flyout/entity_details/host_details_left'; +import { + EntityDetailsLeftPanelTab, + CspInsightLeftPanelSubTab, +} from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; +import { UserDetailsPanelKey } from '../../../flyout/entity_details/user_details_left'; const AlertsCount = ({ alertsTotal, @@ -56,9 +77,13 @@ const AlertsCount = ({ export const AlertsPreview = ({ alertsData, + fieldName, + name, isPreviewMode, }: { alertsData: ParsedAlertsData; + fieldName: string; + name: string; isPreviewMode?: boolean; }) => { const { euiTheme } = useEuiTheme(); @@ -82,9 +107,120 @@ export const AlertsPreview = ({ const totalAlertsCount = alertStats.reduce((total, item) => total + item.count, 0); + const { data } = useMisconfigurationPreview({ + query: buildEntityFlyoutPreviewQuery(fieldName, name), + sort: [], + enabled: true, + pageSize: 1, + ignore_unavailable: true, + }); + const isUsingHostName = fieldName === 'host.name'; + const passedFindings = data?.count.passed || 0; + const failedFindings = data?.count.failed || 0; + + const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; + + const { data: vulnerabilitiesData } = useVulnerabilitiesPreview({ + query: buildEntityFlyoutPreviewQuery('host.name', name), + sort: [], + enabled: true, + pageSize: 1, + }); + + const { + CRITICAL = 0, + HIGH = 0, + MEDIUM = 0, + LOW = 0, + NONE = 0, + } = vulnerabilitiesData?.count || {}; + + const hasVulnerabilitiesFindings = hasVulnerabilitiesData({ + critical: CRITICAL, + high: HIGH, + medium: MEDIUM, + low: LOW, + none: NONE, + }); + + const buildFilterQuery = useMemo( + () => (isUsingHostName ? buildHostNamesFilter([name]) : buildUserNamesFilter([name])), + [isUsingHostName, name] + ); + + const riskScoreState = useRiskScore({ + riskEntity: isUsingHostName ? RiskScoreEntity.host : RiskScoreEntity.user, + filterQuery: buildFilterQuery, + onlyLatest: false, + pagination: FIRST_RECORD_PAGINATION, + }); + + const { data: hostRisk } = riskScoreState; + + const riskData = hostRisk?.[0]; + + const isRiskScoreExist = isUsingHostName + ? !!(riskData as HostRiskScore)?.host.risk + : !!(riskData as UserRiskScore)?.user.risk; + + const hasNonClosedAlerts = totalAlertsCount > 0; + + const { openLeftPanel } = useExpandableFlyoutApi(); + + const goToEntityInsightTab = useCallback(() => { + openLeftPanel({ + id: isUsingHostName ? HostDetailsPanelKey : UserDetailsPanelKey, + params: isUsingHostName + ? { + name, + isRiskScoreExist, + hasMisconfigurationFindings, + hasVulnerabilitiesFindings, + hasNonClosedAlerts, + path: { + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.ALERTS, + }, + } + : { + user: { name }, + isRiskScoreExist, + hasMisconfigurationFindings, + hasNonClosedAlerts, + path: { + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.ALERTS, + }, + }, + }); + }, [ + hasMisconfigurationFindings, + hasNonClosedAlerts, + hasVulnerabilitiesFindings, + isRiskScoreExist, + isUsingHostName, + name, + openLeftPanel, + ]); + const link = useMemo( + () => + !isPreviewMode + ? { + callback: goToEntityInsightTab, + tooltip: ( + + ), + } + : undefined, + [isPreviewMode, goToEntityInsightTab] + ); return ( ), + link: totalAlertsCount > 0 ? link : undefined, }} data-test-subj={'securitySolutionFlyoutInsightsAlerts'} > diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx new file mode 100644 index 0000000000000..966de68e3497f --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx @@ -0,0 +1,265 @@ +/* + * 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, { memo, useCallback, useEffect, useState } from 'react'; +import { capitalize } from 'lodash'; +import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon, EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { DistributionBar } from '@kbn/security-solution-distribution-bar'; +import { + ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS, + uiMetricService, +} from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { buildEntityAlertsQuery } from '@kbn/cloud-security-posture-common/utils/helpers'; +import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { TableId } from '@kbn/securitysolution-data-table'; +import { + OPEN_IN_ALERTS_TITLE_HOSTNAME, + OPEN_IN_ALERTS_TITLE_STATUS, + OPEN_IN_ALERTS_TITLE_USERNAME, +} from '../../../overview/components/detection_response/translations'; +import { useNavigateToAlertsPageWithFilters } from '../../../common/hooks/use_navigate_to_alerts_page_with_filters'; +import { DocumentDetailsPreviewPanelKey } from '../../../flyout/document_details/shared/constants/panel_keys'; +import { useGlobalTime } from '../../../common/containers/use_global_time'; +import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; +import { ALERTS_QUERY_NAMES } from '../../../detections/containers/detection_engine/alerts/constants'; +import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index'; +import { getSeverityColor } from '../../../detections/components/alerts_kpis/severity_level_panel/helpers'; +import { SeverityBadge } from '../../../common/components/severity_badge'; +import { ALERT_PREVIEW_BANNER } from '../../../flyout/document_details/preview/constants'; +import { FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types'; + +type AlertSeverity = 'low' | 'medium' | 'high' | 'critical'; + +interface ResultAlertsField { + _id: string[]; + _index: string[]; + 'kibana.alert.rule.uuid': string[]; + 'kibana.alert.severity': AlertSeverity[]; + 'kibana.alert.rule.name': string[]; + 'kibana.alert.workflow_status': string[]; +} + +interface ContextualFlyoutAlertsField { + id: string; + index: string; + ruleUuid: string; + ruleName: string; + severity: AlertSeverity; + status: string; +} + +interface AlertsDetailsFields { + fields: ResultAlertsField; +} + +export const AlertsDetailsTable = memo( + ({ fieldName, queryName }: { fieldName: 'host.name' | 'user.name'; queryName: string }) => { + useEffect(() => { + uiMetricService.trackUiMetric( + METRIC_TYPE.COUNT, + ENTITY_FLYOUT_EXPAND_MISCONFIGURATION_VIEW_VISITS + ); + }, []); + + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + + const alertsPagination = (alerts: ContextualFlyoutAlertsField[]) => { + let pageOfItems; + + if (!pageIndex && !pageSize) { + pageOfItems = alerts; + } else { + const startIndex = pageIndex * pageSize; + pageOfItems = alerts?.slice(startIndex, Math.min(startIndex + pageSize, alerts?.length)); + } + + return { + pageOfItems, + totalItemCount: alerts?.length, + }; + }; + + const { to, from } = useGlobalTime(); + const { signalIndexName } = useSignalIndex(); + const { data } = useQueryAlerts({ + query: buildEntityAlertsQuery(fieldName, to, from, queryName, 500), + queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS, + indexName: signalIndexName, + }); + + const alertDataResults = (data?.hits?.hits as AlertsDetailsFields[])?.map( + (item: AlertsDetailsFields) => { + return { + id: item.fields?._id?.[0], + index: item.fields?._index?.[0], + ruleName: item.fields?.['kibana.alert.rule.name']?.[0], + ruleUuid: item.fields?.['kibana.alert.rule.uuid']?.[0], + severity: item.fields?.['kibana.alert.severity']?.[0], + status: item.fields?.['kibana.alert.workflow_status']?.[0], + }; + } + ); + + const severitiesMap = alertDataResults?.map((item) => item.severity) || []; + + const alertStats = Object.entries( + severitiesMap.reduce((acc: Record, item) => { + acc[item] = (acc[item] || 0) + 1; + return acc; + }, {}) + ).map(([key, count]) => ({ + key: capitalize(key), + count, + color: getSeverityColor(key), + })); + + const { pageOfItems, totalItemCount } = alertsPagination(alertDataResults || []); + + const pagination = { + pageIndex, + pageSize, + totalItemCount, + pageSizeOptions: [10, 25, 100], + }; + + const onTableChange = ({ page }: Criteria) => { + if (page) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + }; + + const { openPreviewPanel } = useExpandableFlyoutApi(); + + const handleOnEventAlertDetailPanelOpened = useCallback( + (eventId: string, indexName: string, tableId: string) => { + openPreviewPanel({ + id: DocumentDetailsPreviewPanelKey, + params: { + id: eventId, + indexName, + scopeId: tableId, + isPreviewMode: true, + banner: ALERT_PREVIEW_BANNER, + }, + }); + }, + [openPreviewPanel] + ); + + const tableId = TableId.alertsOnRuleDetailsPage; + + const columns: Array> = [ + { + field: 'id', + name: '', + width: '5%', + render: (id: string, alert: ContextualFlyoutAlertsField) => ( + handleOnEventAlertDetailPanelOpened(id, alert.index, tableId)}> + + + ), + }, + { + field: 'ruleName', + render: (ruleName: string) => {ruleName}, + name: i18n.translate( + 'xpack.securitySolution.flyout.left.insights.alerts.table.ruleNameColumnName', + { + defaultMessage: 'Rule', + } + ), + width: '55%', + }, + { + field: 'severity', + render: (severity: AlertSeverity) => ( + + + + ), + name: i18n.translate( + 'xpack.securitySolution.flyout.left.insights.alerts.table.severityColumnName', + { + defaultMessage: 'Severity', + } + ), + width: '20%', + }, + { + field: 'status', + render: (status: string) => {capitalize(status)}, + name: i18n.translate( + 'xpack.securitySolution.flyout.left.insights.alerts.table.statusColumnName', + { + defaultMessage: 'Status', + } + ), + width: '20%', + }, + ]; + + const openAlertsPageWithFilters = useNavigateToAlertsPageWithFilters(); + + const openAlertsInAlertsPage = useCallback( + () => + openAlertsPageWithFilters( + [ + { + title: + fieldName === 'host.name' + ? OPEN_IN_ALERTS_TITLE_HOSTNAME + : OPEN_IN_ALERTS_TITLE_USERNAME, + selectedOptions: [queryName], + fieldName, + }, + { + title: OPEN_IN_ALERTS_TITLE_STATUS, + selectedOptions: [FILTER_OPEN, FILTER_ACKNOWLEDGED], + fieldName: 'kibana.alert.workflow_status', + }, + ], + true + ), + [fieldName, openAlertsPageWithFilters, queryName] + ); + + return ( + <> + + openAlertsInAlertsPage()}> +

+ {i18n.translate('xpack.securitySolution.flyout.left.insights.alerts.tableTitle', { + defaultMessage: 'Alerts ', + })} + +

+
+ + + + + +
+ + ); + } +); + +AlertsDetailsTable.displayName = 'AlertsDetailsTable'; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx index 05421cfa7a208..2e7b4171fd023 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/csp_details/insights_tab_csp.tsx @@ -12,10 +12,10 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout'; import { useExpandableFlyoutState } from '@kbn/expandable-flyout'; import { i18n } from '@kbn/i18n'; -// import type { FlyoutPanels } from '@kbn/expandable-flyout/src/store/state'; import { CspInsightLeftPanelSubTab } from '../../../flyout/entity_details/shared/components/left_panel/left_panel_header'; import { MisconfigurationFindingsDetailsTable } from './misconfiguration_findings_details_table'; import { VulnerabilitiesFindingsDetailsTable } from './vulnerabilities_findings_details_table'; +import { AlertsDetailsTable } from './alerts_findings_details_table'; /** * Insights view displayed in the document details expandable flyout left section @@ -26,6 +26,7 @@ interface CspFlyoutPanelProps extends FlyoutPanelProps { path: PanelPath; hasMisconfigurationFindings: boolean; hasVulnerabilitiesFindings: boolean; + hasNonClosedAlerts: boolean; }; } @@ -35,7 +36,8 @@ function isCspFlyoutPanelProps( ): panelLeft is CspFlyoutPanelProps { return ( !!panelLeft?.params?.hasMisconfigurationFindings || - !!panelLeft?.params?.hasVulnerabilitiesFindings + !!panelLeft?.params?.hasVulnerabilitiesFindings || + !!panelLeft?.params?.hasNonClosedAlerts ); } @@ -45,12 +47,14 @@ export const InsightsTabCsp = memo( let hasMisconfigurationFindings = false; let hasVulnerabilitiesFindings = false; + let hasNonClosedAlerts = false; let subTab: string | undefined; // Check if panels.left is of type CspFlyoutPanelProps and extract values if (isCspFlyoutPanelProps(panels.left)) { hasMisconfigurationFindings = panels.left.params.hasMisconfigurationFindings; hasVulnerabilitiesFindings = panels.left.params.hasVulnerabilitiesFindings; + hasNonClosedAlerts = panels.left.params.hasNonClosedAlerts; subTab = panels.left.params.path?.subTab; } @@ -63,6 +67,8 @@ export const InsightsTabCsp = memo( ? CspInsightLeftPanelSubTab.MISCONFIGURATIONS : hasVulnerabilitiesFindings ? CspInsightLeftPanelSubTab.VULNERABILITIES + : hasNonClosedAlerts + ? CspInsightLeftPanelSubTab.ALERTS : ''; }; @@ -71,6 +77,19 @@ export const InsightsTabCsp = memo( const insightsButtons: EuiButtonGroupOptionProps[] = useMemo(() => { const buttons: EuiButtonGroupOptionProps[] = []; + if (panels.left?.params?.hasNonClosedAlerts) { + buttons.push({ + id: CspInsightLeftPanelSubTab.ALERTS, + label: ( + + ), + 'data-test-subj': 'alertsTabDataTestId', + }); + } + if (panels.left?.params?.hasMisconfigurationFindings) { buttons.push({ id: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, @@ -96,9 +115,11 @@ export const InsightsTabCsp = memo( 'data-test-subj': 'vulnerabilitiesTabDataTestId', }); } + return buttons; }, [ panels.left?.params?.hasMisconfigurationFindings, + panels.left?.params?.hasNonClosedAlerts, panels.left?.params?.hasVulnerabilitiesFindings, ]); @@ -130,8 +151,10 @@ export const InsightsTabCsp = memo( {activeInsightsId === CspInsightLeftPanelSubTab.MISCONFIGURATIONS ? ( - ) : ( + ) : activeInsightsId === CspInsightLeftPanelSubTab.VULNERABILITIES ? ( + ) : ( + )} ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx index a43b56876f1ab..7139994f7e972 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/entity_insight.tsx @@ -94,7 +94,12 @@ export const EntityInsight = ({ if (alertsCount > 0) { insightContent.push( <> - + ); @@ -103,14 +108,23 @@ export const EntityInsight = ({ if (hasMisconfigurationFindings) insightContent.push( <> - + 0} + isPreviewMode={isPreviewMode} + /> ); if (isVulnerabilitiesFindingForHost && hasVulnerabilitiesFindings) insightContent.push( <> - + 0} + /> ); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx index b133e9db22050..42a5906ce4e36 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.tsx @@ -103,10 +103,12 @@ const MisconfigurationPreviewScore = ({ export const MisconfigurationsPreview = ({ name, fieldName, + hasNonClosedAlerts = false, isPreviewMode, }: { name: string; fieldName: 'host.name' | 'user.name'; + hasNonClosedAlerts?: boolean; isPreviewMode?: boolean; }) => { const { data } = useMisconfigurationPreview({ @@ -180,6 +182,7 @@ export const MisconfigurationsPreview = ({ isRiskScoreExist, hasMisconfigurationFindings, hasVulnerabilitiesFindings, + hasNonClosedAlerts, path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, @@ -189,11 +192,16 @@ export const MisconfigurationsPreview = ({ user: { name }, isRiskScoreExist, hasMisconfigurationFindings, - path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS }, + hasNonClosedAlerts, + path: { + tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, + subTab: CspInsightLeftPanelSubTab.MISCONFIGURATIONS, + }, }, }); }, [ hasMisconfigurationFindings, + hasNonClosedAlerts, hasVulnerabilitiesFindings, isRiskScoreExist, isUsingHostName, diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx index a9ddaff62085b..c4335d921e371 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx @@ -72,9 +72,11 @@ const VulnerabilitiesCount = ({ export const VulnerabilitiesPreview = ({ name, isPreviewMode, + hasNonClosedAlerts = false, }: { name: string; isPreviewMode?: boolean; + hasNonClosedAlerts?: boolean; }) => { useEffect(() => { uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW); @@ -132,11 +134,13 @@ export const VulnerabilitiesPreview = ({ isRiskScoreExist, hasMisconfigurationFindings, hasVulnerabilitiesFindings, + hasNonClosedAlerts, path: { tab: EntityDetailsLeftPanelTab.CSP_INSIGHTS, subTab: 'vulnerabilitiesTabId' }, }, }); }, [ hasMisconfigurationFindings, + hasNonClosedAlerts, hasVulnerabilitiesFindings, isRiskScoreExist, name, diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts index 6a957318f278f..3bfc0c56e81fa 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts @@ -32,6 +32,7 @@ describe('useNavigateToAlertsPageWithFilters', () => { expect(mockNavigateTo).toHaveBeenCalledWith({ deepLinkId: SecurityPageName.alerts, path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field',hideActionBar:!f,selectedOptions:!('test value'),title:'test filter'))", + openInNewTab: false, }); }); @@ -63,6 +64,7 @@ describe('useNavigateToAlertsPageWithFilters', () => { expect(mockNavigateTo).toHaveBeenCalledWith({ deepLinkId: SecurityPageName.alerts, path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field 1',hideActionBar:!f,selectedOptions:!('test value 1'),title:'test filter 1'),(exclude:!t,existsSelected:!t,fieldName:'test field 2',hideActionBar:!t,selectedOptions:!('test value 2'),title:'test filter 2'))", + openInNewTab: false, }); }); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts index fffa65797b3f8..037bac32d8c92 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts @@ -16,7 +16,7 @@ import { URL_PARAM_KEY } from './use_url_state'; export const useNavigateToAlertsPageWithFilters = () => { const { navigateTo } = useNavigation(); - return (filterItems: FilterControlConfig | FilterControlConfig[]) => { + return (filterItems: FilterControlConfig | FilterControlConfig[], openInNewTab = false) => { const urlFilterParams = encode( formatPageFilterSearchParam(Array.isArray(filterItems) ? filterItems : [filterItems]) ); @@ -24,6 +24,7 @@ export const useNavigateToAlertsPageWithFilters = () => { navigateTo({ deepLinkId: SecurityPageName.alerts, path: `?${URL_PARAM_KEY.pageFilter}=${urlFilterParams}`, + openInNewTab, }); }; }; diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx index 6e5774ba1756e..107cb83ddd97b 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_details_left/index.tsx @@ -25,6 +25,7 @@ export interface HostDetailsPanelProps extends Record { scopeId: string; hasMisconfigurationFindings?: boolean; hasVulnerabilitiesFindings?: boolean; + hasNonClosedAlerts?: boolean; path?: { tab?: EntityDetailsLeftPanelTab; subTab?: CspInsightLeftPanelSubTab; @@ -43,6 +44,7 @@ export const HostDetailsPanel = ({ path, hasMisconfigurationFindings, hasVulnerabilitiesFindings, + hasNonClosedAlerts, }: HostDetailsPanelProps) => { const [selectedTabId, setSelectedTabId] = useState( path?.tab === EntityDetailsLeftPanelTab.CSP_INSIGHTS @@ -58,11 +60,18 @@ export const HostDetailsPanel = ({ // Determine if the Insights tab should be included const insightsTab = - hasMisconfigurationFindings || hasVulnerabilitiesFindings + hasMisconfigurationFindings || hasVulnerabilitiesFindings || hasNonClosedAlerts ? [getInsightsInputTab({ name, fieldName: 'host.name' })] : []; return [[...riskScoreTab, ...insightsTab], EntityDetailsLeftPanelTab.RISK_INPUTS, () => {}]; - }, [isRiskScoreExist, name, scopeId, hasMisconfigurationFindings, hasVulnerabilitiesFindings]); + }, [ + isRiskScoreExist, + name, + scopeId, + hasMisconfigurationFindings, + hasVulnerabilitiesFindings, + hasNonClosedAlerts, + ]); return ( <> diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx index 83fa75474a1cc..a7e99898606f8 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/index.tsx @@ -13,6 +13,8 @@ import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-commo import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; import { sum } from 'lodash'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types'; +import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status'; import { useRefetchQueryById } from '../../../entity_analytics/api/hooks/use_refetch_query_by_id'; import { RISK_INPUTS_TAB_QUERY_ID } from '../../../entity_analytics/components/entity_details_flyout/tabs/risk_inputs/risk_inputs_tab'; import type { Refetch } from '../../../common/types'; @@ -35,6 +37,7 @@ import { useObservedHost } from './hooks/use_observed_host'; import { HostDetailsPanelKey } from '../host_details_left'; import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; import { HostPreviewPanelFooter } from '../host_preview/footer'; +import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index'; import { EntityEventTypes } from '../../../common/lib/telemetry'; export interface HostPanelProps extends Record { @@ -120,6 +123,21 @@ export const HostPanel = ({ const hasVulnerabilitiesFindings = sum(Object.values(vulnerabilitiesData?.count || {})) > 0; + const { signalIndexName } = useSignalIndex(); + + const entityFilter = useMemo(() => ({ field: 'host.name', value: hostName }), [hostName]); + + const { items: alertsData } = useAlertsByStatus({ + entityFilter, + signalIndexName, + queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}HOST_NAME_RIGHT`, + to, + from, + }); + + const hasNonClosedAlerts = + (alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0; + useQueryInspector({ deleteQuery, inspect: inspectRiskScore, @@ -144,6 +162,7 @@ export const HostPanel = ({ path: tab ? { tab } : undefined, hasMisconfigurationFindings, hasVulnerabilitiesFindings, + hasNonClosedAlerts, }, }); }, @@ -155,6 +174,7 @@ export const HostPanel = ({ isRiskScoreExist, hasMisconfigurationFindings, hasVulnerabilitiesFindings, + hasNonClosedAlerts, ] ); diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx index 08623c941ba67..254985b865840 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/shared/components/left_panel/left_panel_header.tsx @@ -28,6 +28,7 @@ export enum EntityDetailsLeftPanelTab { export enum CspInsightLeftPanelSubTab { MISCONFIGURATIONS = 'misconfigurationTabId', VULNERABILITIES = 'vulnerabilitiesTabId', + ALERTS = 'alertsTabId', } export interface PanelHeaderProps { diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx index 8e6cf3a9ee9d2..87c9e5abc7afd 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/index.tsx @@ -29,6 +29,7 @@ export interface UserDetailsPanelProps extends Record { path?: PanelPath; scopeId: string; hasMisconfigurationFindings?: boolean; + hasNonClosedAlerts?: boolean; } export interface UserDetailsExpandableFlyoutProps extends FlyoutPanelProps { key: 'user_details'; @@ -42,6 +43,7 @@ export const UserDetailsPanel = ({ path, scopeId, hasMisconfigurationFindings, + hasNonClosedAlerts, }: UserDetailsPanelProps) => { const managedUser = useManagedUser(user.name, user.email); const tabs = useTabs( @@ -49,7 +51,8 @@ export const UserDetailsPanel = ({ user.name, isRiskScoreExist, scopeId, - hasMisconfigurationFindings + hasMisconfigurationFindings, + hasNonClosedAlerts ); const { selectedTabId, setSelectedTabId } = useSelectedTab( @@ -57,7 +60,8 @@ export const UserDetailsPanel = ({ user, tabs, path, - hasMisconfigurationFindings + hasMisconfigurationFindings, + hasNonClosedAlerts ); if (managedUser.isLoading) return ; @@ -83,7 +87,8 @@ const useSelectedTab = ( user: UserParam, tabs: LeftPanelTabsType, path: PanelPath | undefined, - hasMisconfigurationFindings?: boolean + hasMisconfigurationFindings?: boolean, + hasNonClosedAlerts?: boolean ) => { const { openLeftPanel } = useExpandableFlyoutApi(); @@ -101,6 +106,7 @@ const useSelectedTab = ( user, isRiskScoreExist, hasMisconfigurationFindings, + hasNonClosedAlerts, path: { tab: tabId, }, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx index 6f27b054759f2..0c1cdcaa904a9 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_details_left/tabs.tsx @@ -30,7 +30,8 @@ export const useTabs = ( name: string, isRiskScoreExist: boolean, scopeId: string, - hasMisconfigurationFindings?: boolean + hasMisconfigurationFindings?: boolean, + hasNonClosedAlerts?: boolean ): LeftPanelTabsType => useMemo(() => { const tabs: LeftPanelTabsType = []; @@ -55,12 +56,19 @@ export const useTabs = ( tabs.push(getEntraTab(entraManagedUser)); } - if (hasMisconfigurationFindings) { + if (hasMisconfigurationFindings || hasNonClosedAlerts) { tabs.push(getInsightsInputTab({ name, fieldName: 'user.name' })); } return tabs; - }, [hasMisconfigurationFindings, isRiskScoreExist, managedUser, name, scopeId]); + }, [ + hasMisconfigurationFindings, + hasNonClosedAlerts, + isRiskScoreExist, + managedUser, + name, + scopeId, + ]); const getOktaTab = (oktaManagedUser: ManagedUserHit) => ({ id: EntityDetailsLeftPanelTab.OKTA, diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx index 42c8664b2ac0c..07762ed9aea0c 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/user_right/index.tsx @@ -33,6 +33,9 @@ import { UserDetailsPanelKey } from '../user_details_left'; import { useObservedUser } from './hooks/use_observed_user'; import { EntityDetailsLeftPanelTab } from '../shared/components/left_panel/left_panel_header'; import { UserPreviewPanelFooter } from '../user_preview/footer'; +import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index'; +import { useAlertsByStatus } from '../../../overview/components/detection_response/alerts_by_status/use_alerts_by_status'; +import { DETECTION_RESPONSE_ALERTS_BY_STATUS_ID } from '../../../overview/components/detection_response/alerts_by_status/types'; import { EntityEventTypes } from '../../../common/lib/telemetry'; export interface UserPanelProps extends Record { @@ -112,6 +115,21 @@ export const UserPanel = ({ const hasMisconfigurationFindings = passedFindings > 0 || failedFindings > 0; + const { signalIndexName } = useSignalIndex(); + + const entityFilter = useMemo(() => ({ field: 'user.name', value: userName }), [userName]); + + const { items: alertsData } = useAlertsByStatus({ + entityFilter, + signalIndexName, + queryId: `${DETECTION_RESPONSE_ALERTS_BY_STATUS_ID}USER_NAME_RIGHT`, + to, + from, + }); + + const hasNonClosedAlerts = + (alertsData?.acknowledged?.total || 0) + (alertsData?.open?.total || 0) > 0; + useQueryInspector({ deleteQuery, inspect, @@ -139,6 +157,7 @@ export const UserPanel = ({ }, path: tab ? { tab } : undefined, hasMisconfigurationFindings, + hasNonClosedAlerts, }, }); }, @@ -150,6 +169,7 @@ export const UserPanel = ({ userName, email, hasMisconfigurationFindings, + hasNonClosedAlerts, ] ); const openPanelFirstTab = useCallback( @@ -191,7 +211,8 @@ export const UserPanel = ({ <> From 364b226019169d055fd11ee2bbd100c2ce3cfe4c Mon Sep 17 00:00:00 2001 From: Alex Szabo Date: Thu, 14 Nov 2024 20:58:21 +0100 Subject: [PATCH 22/33] [CI] Fix OAS snapshot case (#200241) ## Summary Fixes currently broken on-merge by committing the OAS update from capture_oas_snapshot.sh --- oas_docs/output/kibana.serverless.yaml | 78 +++++---------------- oas_docs/output/kibana.yaml | 96 ++++++-------------------- 2 files changed, 40 insertions(+), 134 deletions(-) diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 117e52586c5ad..4f54e401b14c2 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -1018,24 +1018,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -1610,24 +1603,17 @@ paths: type: boolean flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch states - in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -1945,24 +1931,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -2540,24 +2519,17 @@ paths: - active flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch states - in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -2847,24 +2819,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -3902,24 +3867,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index ceefaa13fcd4b..cb7d39cae0cab 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -1367,24 +1367,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -1958,24 +1951,17 @@ paths: type: boolean flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch states - in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -2293,24 +2279,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -2887,24 +2866,17 @@ paths: - active flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch states - in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -3194,24 +3166,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -4241,24 +4206,17 @@ paths: - last_execution_date flapping: additionalProperties: false - description: >- - When flapping detection is turned on, alerts that switch - quickly between active and recovered states are identified - as โ€œflappingโ€ and notifications are reduced. + description: When flapping detection is turned on, alerts that switch quickly between active and recovered states are identified as โ€œflappingโ€ and notifications are reduced. nullable: true type: object properties: look_back_window: - description: >- - The minimum number of runs in which the threshold must - be met. + description: The minimum number of runs in which the threshold must be met. maximum: 20 minimum: 2 type: number status_change_threshold: - description: >- - The minimum number of times an alert must switch - states in the look back window. + description: The minimum number of times an alert must switch states in the look back window. maximum: 20 minimum: 2 type: number @@ -6708,14 +6666,9 @@ paths: - cases /api/cases/{caseId}/files: post: - description: > - Attach a file to a case. You must have `all` privileges for the - **Cases** feature in the **Management**, **Observability**, or - **Security** section of the Kibana feature privileges, depending on the - owner of the case you're updating. The request must include: - + description: | + Attach a file to a case. You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. The request must include: - The `Content-Type: multipart/form-data` HTTP header. - - The location of the file that is being uploaded. operationId: addCaseFileDefaultSpace parameters: @@ -43715,9 +43668,7 @@ components: - $ref: '#/components/schemas/Cases_add_user_comment_request_properties' title: Add case comment request Cases_add_case_file_request: - description: >- - Defines the file that will be attached to the case. Optional parameters - will be generated automatically from the file metadata if not defined. + description: Defines the file that will be attached to the case. Optional parameters will be generated automatically from the file metadata if not defined. type: object properties: file: @@ -43725,10 +43676,7 @@ components: format: binary type: string filename: - description: >- - The desired name of the file being attached to the case, it can be - different than the name of the file in the filesystem. **This should - not include the file extension.** + description: The desired name of the file being attached to the case, it can be different than the name of the file in the filesystem. **This should not include the file extension.** type: string required: - file From 44727926214af7c6c9e144ab9ec6e703f433ac44 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:02:22 +1100 Subject: [PATCH 23/33] skip failing test suite (#200020) --- .../functional/test_suites/search/elasticsearch_start.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts index 39228137cf7d7..ba370871c07ff 100644 --- a/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts +++ b/x-pack/test_serverless/functional/test_suites/search/elasticsearch_start.ts @@ -26,7 +26,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await esDeleteAllIndices(['search-*', 'test-*']); }; - describe('Elasticsearch Start [Onboarding Empty State]', function () { + // Failing: See https://github.com/elastic/kibana/issues/200020 + describe.skip('Elasticsearch Start [Onboarding Empty State]', function () { describe('developer', function () { before(async () => { await pageObjects.svlCommonPage.loginWithRole('developer'); From 8ede68b7ec2c1eccc944b921a02db2d5ad9d825a Mon Sep 17 00:00:00 2001 From: wajihaparvez Date: Thu, 14 Nov 2024 16:29:56 -0500 Subject: [PATCH 24/33] [Docs] Update Known Issues (#200240) ## Summary Fixed some typos and updated sentence structure --- docs/CHANGELOG.asciidoc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 1b45c8700d9a3..85c5bcfbf1127 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -93,37 +93,36 @@ The 8.16.0 release includes the following known issues. [discrete] [[known-199902]] -.Stack Monitoring shows "Unable to load page error" +.Stack Monitoring shows "Unable to load page" error [%collapsible] ==== *Details* + -The Stack Monitoring pages Overview, Nodes, Logs can stop working with the error "Unable to load page error". The Stack trace mentions `TypeError: Cannot read properties of undefined (reading 'logsLocator')`. +The Overview, Nodes, and Logs pages in Stack Monitoring show an "Unable to load page" error. The Stack trace mentions `TypeError: Cannot read properties of undefined (reading 'logsLocator')`. *Workaround* + -Disabling the `Set feature visibility > Logs` feature at Kibana Space settings level will prevent the error to occur. Please note the `Logs` feature will not be available on such space. +Disabling the `Set feature visibility > Logs` feature at the Kibana Space settings level will prevent the error from occurring. Please note the `Logs` feature will not be available on those spaces. -It's also possible to `Observability > Logs` feature privilege to `None` on the role level - this will hide the `Logs` feature for individual users and prevent the error for these users as well. +It's also possible to set the `Observability > Logs` feature privilege to `None` at the role level. This will hide the `Logs` feature from individual users and prevent the error for these users as well. For more information, refer to {kibana-issue}199902[#199902]. ==== [discrete] [[known-199891-199892]] -.Onboarding, tutorial of APM and OpenTelemetry and some "Beats Only" integrations will show the error "Unable to load page error" +.Onboarding, tutorial of APM and OpenTelemetry and some "Beats Only" integrations shows "Unable to load page" error [%collapsible] ==== *Details* + -Tutorials linked from the {kib} home page show an error "Unable to load page error". The Stack trace mentions `The above error occurred in tutorial_TutorialUi`. +Tutorials linked from the {kib} home page show an "Unable to load page" error. The Stack trace mentions `The above error occurred in tutorial_TutorialUi`. *Workaround* + -The APM / OpenTelemetry tutorials represented a shortcut to get important parameters to use in the configuration files quickly. -It is still possible to obtain the same parameters following the documentation tutorials of APM. +The APM / OpenTelemetry tutorials represented a shortcut to quickly add important parameters to the configuration files. +It is still possible to obtain the same parameters following the tutorials in the APM documentation. More information can be found in the {observability-guide}/apm-collect-application-data.html[APM documentation] and the {observability-guide}/get-started-with-fleet-apm-server.html[Fleet documentation]. For information about how to create APM API keys, please check the {observability-guide}/apm-api-key.html#apm-create-an-api-key[API key documentation]. - For more information, refer to {kibana-issue}199891[#199891] and {kibana-issue}199892[#199892]. ==== From ba6ffec9e769669bce8a265cfb55fc142471fbe0 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 14 Nov 2024 14:05:39 -0800 Subject: [PATCH 25/33] [OpenAPI] Fix fleet filepath API parameter (#199538) --- oas_docs/bundle.json | 2 +- oas_docs/bundle.serverless.json | 2 +- oas_docs/output/kibana.serverless.yaml | 2 +- oas_docs/output/kibana.yaml | 2 +- .../src/__snapshots__/generate_oas.test.ts.snap | 4 ++-- .../src/generate_oas.test.fixture.ts | 2 +- .../src/generate_oas.test.ts | 14 +++++++------- .../kbn-router-to-openapispec/src/util.test.ts | 7 +++++-- packages/kbn-router-to-openapispec/src/util.ts | 2 +- 9 files changed, 20 insertions(+), 17 deletions(-) diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index a4c1e30c8cf05..d30ac3c4552e2 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -21335,7 +21335,7 @@ ] } }, - "/api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath*}": { + "/api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath}": { "get": { "operationId": "get-fleet-epm-packages-pkgname-pkgversion-filepath", "parameters": [ diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index a0bd0c4cc4340..4b56e3581c66f 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -21335,7 +21335,7 @@ ] } }, - "/api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath*}": { + "/api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath}": { "get": { "operationId": "get-fleet-epm-packages-pkgname-pkgversion-filepath", "parameters": [ diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 4f54e401b14c2..55dd5277d1d93 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -19323,7 +19323,7 @@ paths: tags: - Elastic Package Manager (EPM) x-beta: true - /api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath*}: + /api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath}: get: operationId: get-fleet-epm-packages-pkgname-pkgversion-filepath parameters: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index cb7d39cae0cab..b2c3ae00be9d0 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -22127,7 +22127,7 @@ paths: summary: Update package settings tags: - Elastic Package Manager (EPM) - /api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath*}: + /api/fleet/epm/packages/{pkgName}/{pkgVersion}/{filePath}: get: operationId: get-fleet-epm-packages-pkgname-pkgversion-filepath parameters: diff --git a/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap b/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap index 1c1f5bed02a0e..ccf5fad20a71a 100644 --- a/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap +++ b/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap @@ -228,7 +228,7 @@ OK response oas-test-version-2", "x-discontinued": "route discontinued version or date", }, }, - "/foo/{id}/{path*}": Object { + "/foo/{id}/{path}": Object { "delete": Object { "description": "route description", "operationId": "delete-foo-id-path", @@ -570,7 +570,7 @@ OK response oas-test-version-2", ], }, }, - "/no-xsrf/{id}/{path*}": Object { + "/no-xsrf/{id}/{path}": Object { "post": Object { "deprecated": true, "operationId": "post-no-xsrf-id-path-2", diff --git a/packages/kbn-router-to-openapispec/src/generate_oas.test.fixture.ts b/packages/kbn-router-to-openapispec/src/generate_oas.test.fixture.ts index f4ba66f992134..dedba5036d7ef 100644 --- a/packages/kbn-router-to-openapispec/src/generate_oas.test.fixture.ts +++ b/packages/kbn-router-to-openapispec/src/generate_oas.test.fixture.ts @@ -151,7 +151,7 @@ export const sharedOas = { tags: ['versioned'], }, }, - '/foo/{id}/{path*}': { + '/foo/{id}/{path}': { get: { description: 'route description', operationId: 'get-foo-id-path', diff --git a/packages/kbn-router-to-openapispec/src/generate_oas.test.ts b/packages/kbn-router-to-openapispec/src/generate_oas.test.ts index 25b786ac7c2c7..76a4f560006b3 100644 --- a/packages/kbn-router-to-openapispec/src/generate_oas.test.ts +++ b/packages/kbn-router-to-openapispec/src/generate_oas.test.ts @@ -314,9 +314,9 @@ describe('generateOpenApiDocument', () => { } ); // router paths - expect(result.paths['/1-1/{id}/{path*}']!.get!.tags).toEqual(['1', '2']); - expect(result.paths['/1-2/{id}/{path*}']!.get!.tags).toEqual(['1']); - expect(result.paths['/2-1/{id}/{path*}']!.get!.tags).toEqual([]); + expect(result.paths['/1-1/{id}/{path}']!.get!.tags).toEqual(['1', '2']); + expect(result.paths['/1-2/{id}/{path}']!.get!.tags).toEqual(['1']); + expect(result.paths['/2-1/{id}/{path}']!.get!.tags).toEqual([]); // versioned router paths expect(result.paths['/v1-1']!.get!.tags).toEqual(['v1']); expect(result.paths['/v1-2']!.get!.tags).toEqual(['v2', 'v3']); @@ -392,17 +392,17 @@ describe('generateOpenApiDocument', () => { ); // router paths - expect(result.paths['/1-1/{id}/{path*}']!.get).toMatchObject({ + expect(result.paths['/1-1/{id}/{path}']!.get).toMatchObject({ 'x-state': 'Technical Preview', }); - expect(result.paths['/1-2/{id}/{path*}']!.get).toMatchObject({ + expect(result.paths['/1-2/{id}/{path}']!.get).toMatchObject({ 'x-state': 'Beta', }); - expect(result.paths['/1-3/{id}/{path*}']!.get).not.toMatchObject({ + expect(result.paths['/1-3/{id}/{path}']!.get).not.toMatchObject({ 'x-state': expect.any(String), }); - expect(result.paths['/2-1/{id}/{path*}']!.get).not.toMatchObject({ + expect(result.paths['/2-1/{id}/{path}']!.get).not.toMatchObject({ 'x-state': expect.any(String), }); diff --git a/packages/kbn-router-to-openapispec/src/util.test.ts b/packages/kbn-router-to-openapispec/src/util.test.ts index f9692e57e1f50..e3011aa1a5a73 100644 --- a/packages/kbn-router-to-openapispec/src/util.test.ts +++ b/packages/kbn-router-to-openapispec/src/util.test.ts @@ -17,8 +17,9 @@ import { getPathParameters, createOpIdGenerator, GetOpId, + assignToPaths, + extractTags, } from './util'; -import { assignToPaths, extractTags } from './util'; describe('extractTags', () => { test.each([ @@ -115,9 +116,11 @@ describe('assignToPaths', () => { const paths = {}; assignToPaths(paths, '/foo', {}); assignToPaths(paths, '/bar/{id?}', {}); + assignToPaths(paths, '/bar/file/{path*}', {}); expect(paths).toEqual({ '/foo': {}, '/bar/{id}': {}, + '/bar/file/{path}': {}, }); }); }); @@ -320,7 +323,7 @@ describe('createOpIdGenerator', () => { { input: { method: 'get', - path: '/api/my/resource/{path*}', + path: '/api/my/resource/{path}', }, output: 'get-my-resource-path', }, diff --git a/packages/kbn-router-to-openapispec/src/util.ts b/packages/kbn-router-to-openapispec/src/util.ts index a5718fa92120f..1088259e73d05 100644 --- a/packages/kbn-router-to-openapispec/src/util.ts +++ b/packages/kbn-router-to-openapispec/src/util.ts @@ -132,7 +132,7 @@ export const assignToPaths = ( path: string, pathObject: OpenAPIV3.PathItemObject ): void => { - const pathName = path.replace('?', ''); + const pathName = path.replace(/[\?\*]/, ''); paths[pathName] = { ...paths[pathName], ...pathObject }; }; From 53dd55bf9354a3ef29524b96d271452cb5f80cbd Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Thu, 14 Nov 2024 15:04:34 -0800 Subject: [PATCH 26/33] [Dashboard] Fix incorrect actions in edit mode after discarding unsaved changes (#200230) ## Summary Closes #197870. The `isCompatible` check on the edit drilldown action was throwing an unhandled promise error that prevented the actions for drilldown compatible panels when trying to access `event` off an undefined `dynamicActions` object. This ensures that `dynamicActions` is always a defined object. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_node:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --- x-pack/plugins/embeddable_enhanced/public/plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index f05982af78947..a76f33f095951 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -148,8 +148,8 @@ export class EmbeddableEnhancedPlugin ); const api: DynamicActionStorageApi = { dynamicActionsState$, - setDynamicActions: (newState) => { - dynamicActionsState$.next(newState); + setDynamicActions: (enhancements) => { + dynamicActionsState$.next(getDynamicActionsState(enhancements)); }, }; const storage = new DynamicActionStorage(uuid, getTitle, api); From 767a4bbd406bb5d87ca39d1b9a474820343147bb Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:50:01 +1100 Subject: [PATCH 27/33] Unauthorized route migration for routes owned by kibana-cloud-security-posture (#198353) ### Authz API migration for unauthorized routes This PR migrates unauthorized routes owned by your team to a new security configuration. Please refer to the documentation for more information: [Authorization API](https://docs.elastic.dev/kibana-dev-docs/key-concepts/security-api-authorization) ### **Before migration:** ```ts router.get({ path: '/api/path', ... }, handler); ``` ### **After migration:** ```ts router.get({ path: '/api/path', security: { authz: { enabled: false, reason: 'This route is opted out from authorization because ...', }, }, ... }, handler); ``` ### What to do next? 1. Review the changes in this PR. 2. Elaborate on the reasoning to opt-out of authorization. 3. Routes without a compelling reason to opt-out of authorization should plan to introduce them as soon as possible. 2. You might need to update your tests to reflect the new security configuration: - If you have snapshot tests that include the route definition. ## Any questions? If you have any questions or need help with API authorization, please reach out to the `@elastic/kibana-security` team. --------- Co-authored-by: Paulo Silva --- .../plugins/kubernetes_security/server/routes/aggregate.ts | 5 +++++ x-pack/plugins/kubernetes_security/server/routes/count.ts | 5 +++++ .../server/routes/multi_terms_aggregate.ts | 5 +++++ .../session_view/server/routes/alert_status_route.ts | 5 +++++ x-pack/plugins/session_view/server/routes/alerts_route.ts | 5 +++++ .../session_view/server/routes/get_total_io_bytes_route.ts | 6 ++++++ .../plugins/session_view/server/routes/io_events_route.ts | 6 ++++++ .../session_view/server/routes/process_events_route.ts | 6 ++++++ 8 files changed, 43 insertions(+) diff --git a/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts b/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts index f83ddc818cbb4..4ddb828b68976 100644 --- a/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts +++ b/x-pack/plugins/kubernetes_security/server/routes/aggregate.ts @@ -38,6 +38,11 @@ export const registerAggregateRoute = (router: IRouter, logger: Logger) => { .addVersion( { version: '1', + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/kubernetes_security/server/routes/count.ts b/x-pack/plugins/kubernetes_security/server/routes/count.ts index 0922adeb0cf45..b73452e8e45fc 100644 --- a/x-pack/plugins/kubernetes_security/server/routes/count.ts +++ b/x-pack/plugins/kubernetes_security/server/routes/count.ts @@ -28,6 +28,11 @@ export const registerCountRoute = (router: IRouter, logger: Logger) => { .addVersion( { version: '1', + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/kubernetes_security/server/routes/multi_terms_aggregate.ts b/x-pack/plugins/kubernetes_security/server/routes/multi_terms_aggregate.ts index 83f5b70efe051..b4a0271b63edc 100644 --- a/x-pack/plugins/kubernetes_security/server/routes/multi_terms_aggregate.ts +++ b/x-pack/plugins/kubernetes_security/server/routes/multi_terms_aggregate.ts @@ -35,6 +35,11 @@ export const registerMultiTermsAggregateRoute = (router: IRouter, logger: Logger .addVersion( { version: '1', + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/session_view/server/routes/alert_status_route.ts b/x-pack/plugins/session_view/server/routes/alert_status_route.ts index e0b95f9705e9d..64192198b5e46 100644 --- a/x-pack/plugins/session_view/server/routes/alert_status_route.ts +++ b/x-pack/plugins/session_view/server/routes/alert_status_route.ts @@ -31,6 +31,11 @@ export const registerAlertStatusRoute = ( .addVersion( { version: '1', + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/session_view/server/routes/alerts_route.ts b/x-pack/plugins/session_view/server/routes/alerts_route.ts index c6b7fd8db7896..c875236989efe 100644 --- a/x-pack/plugins/session_view/server/routes/alerts_route.ts +++ b/x-pack/plugins/session_view/server/routes/alerts_route.ts @@ -36,6 +36,11 @@ export const registerAlertsRoute = ( .addVersion( { version: '1', + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts b/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts index 50f36ac47f5a4..7d54654c89cdc 100644 --- a/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts +++ b/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts @@ -22,6 +22,12 @@ export const registerGetTotalIOBytesRoute = (router: IRouter, logger: Logger) => .addVersion( { version: '1', + security: { + authz: { + enabled: false, + reason: `This route delegates authorization to Elasticsearch and it's not tied to a Kibana privilege.`, + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/session_view/server/routes/io_events_route.ts b/x-pack/plugins/session_view/server/routes/io_events_route.ts index 9810f9da5aa77..3e73517a978c3 100644 --- a/x-pack/plugins/session_view/server/routes/io_events_route.ts +++ b/x-pack/plugins/session_view/server/routes/io_events_route.ts @@ -29,6 +29,12 @@ export const registerIOEventsRoute = (router: IRouter, logger: Logger) => { .addVersion( { version: '1', + security: { + authz: { + enabled: false, + reason: `This route delegates authorization to Elasticsearch and it's not tied to a Kibana privilege.`, + }, + }, validate: { request: { query: schema.object({ diff --git a/x-pack/plugins/session_view/server/routes/process_events_route.ts b/x-pack/plugins/session_view/server/routes/process_events_route.ts index bc6b24fc36bc5..b30b3b6ddcc51 100644 --- a/x-pack/plugins/session_view/server/routes/process_events_route.ts +++ b/x-pack/plugins/session_view/server/routes/process_events_route.ts @@ -43,6 +43,12 @@ export const registerProcessEventsRoute = ( .addVersion( { version: '1', + security: { + authz: { + enabled: false, + reason: `This route delegates authorization to Elasticsearch and it's not tied to a Kibana privilege.`, + }, + }, validate: { request: { query: schema.object({ From 31994854d24ea913581850bdf6a43c7f2a225e7c Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:47:38 -0600 Subject: [PATCH 28/33] Update dependency @launchdarkly/node-server-sdk to ^9.7.1 (main) (#199836) --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index fb6b8b093560c..aead0ae928319 100644 --- a/package.json +++ b/package.json @@ -1022,7 +1022,7 @@ "@langchain/langgraph": "0.2.19", "@langchain/openai": "^0.3.11", "@langtrase/trace-attributes": "^3.0.8", - "@launchdarkly/node-server-sdk": "^9.7.0", + "@launchdarkly/node-server-sdk": "^9.7.1", "@launchdarkly/openfeature-node-server": "^1.0.0", "@loaders.gl/core": "^3.4.7", "@loaders.gl/json": "^3.4.7", diff --git a/yarn.lock b/yarn.lock index 2dd177d56de3c..aa8bd6d14de9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7447,25 +7447,25 @@ dependencies: ncp "^2.0.0" -"@launchdarkly/js-sdk-common@2.11.0": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@launchdarkly/js-sdk-common/-/js-sdk-common-2.11.0.tgz#efc0c94ee4b11d72910c5bcdf9294154a27dbb1c" - integrity sha512-96Jg4QH347w2+rL4Bpykqw28+HHUAW4HapjIkIfM3giELK7BwXUp3BiAVxo2ax78e7A7KqvMzPJUx5r2EGpkMw== +"@launchdarkly/js-sdk-common@2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@launchdarkly/js-sdk-common/-/js-sdk-common-2.12.0.tgz#c22eb9fead687260d916a75f693c7d399f085b05" + integrity sha512-HIDxvgo1vksC9hsYy3517sgW0Ql+iW3fgwlq/CEigeBNmaa9/J1Pxo7LrKPzezEA0kaGedmt/DCzVVxVBmxSsQ== -"@launchdarkly/js-server-sdk-common@2.9.0": - version "2.9.0" - resolved "https://registry.yarnpkg.com/@launchdarkly/js-server-sdk-common/-/js-server-sdk-common-2.9.0.tgz#337d13d4bec596f6244b9723f7ec718ee087bfa5" - integrity sha512-hf/qkn+NvCkyoLl6fl+4Q737p4Jg3T+RnRqdkJDMxO+8aAI+vpXuD9bbhkgYA9XFHyQ9puhufRGzX1BX26b7Rg== +"@launchdarkly/js-server-sdk-common@2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@launchdarkly/js-server-sdk-common/-/js-server-sdk-common-2.9.1.tgz#a683d682897c20a6967f5454d932663e4da6fc5c" + integrity sha512-BGIjcfel1hURvX4hM4iVruWecWMntRzh1UuPtV0uOYnXLuETp5lfpqBB6KzFoERnEZoCyX6Tmo+tPFVwteIUGA== dependencies: - "@launchdarkly/js-sdk-common" "2.11.0" + "@launchdarkly/js-sdk-common" "2.12.0" semver "7.5.4" -"@launchdarkly/node-server-sdk@^9.7.0": - version "9.7.0" - resolved "https://registry.yarnpkg.com/@launchdarkly/node-server-sdk/-/node-server-sdk-9.7.0.tgz#87223c2d3ab5fc7186065a0974960c94f73573ad" - integrity sha512-ABOsjcjH9pFdyG1m5++lhP+ngxfx4GMcIfgTp0iSPncuh0dMxCCWSx831gbhxR9M+f2x4EnsQ9HEdwnmwktb9g== +"@launchdarkly/node-server-sdk@^9.7.1": + version "9.7.1" + resolved "https://registry.yarnpkg.com/@launchdarkly/node-server-sdk/-/node-server-sdk-9.7.1.tgz#bb228e5f8c4c7ca52579f909e4150b9a749b04a9" + integrity sha512-razZ/ine5hfHiS7ZNfM1r/G7GNDQ+wHV0pRN2A0Yk5spZTmT/ecu67e+aXu/KAfEQyL0V4ofEkXJpeGl7TYJ5Q== dependencies: - "@launchdarkly/js-server-sdk-common" "2.9.0" + "@launchdarkly/js-server-sdk-common" "2.9.1" https-proxy-agent "^5.0.1" launchdarkly-eventsource "2.0.3" From 26c2b6b3b74a63b829d8e34308c83ab7224ef323 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:50:28 -0600 Subject: [PATCH 29/33] Update dependency @redocly/cli to ^1.25.11 (main) (#200121) --- oas_docs/package-lock.json | 3 ++- oas_docs/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/oas_docs/package-lock.json b/oas_docs/package-lock.json index 6527a6ee6a5dd..70fff86254f65 100644 --- a/oas_docs/package-lock.json +++ b/oas_docs/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@redocly/cli": "^1.25.7", + "@redocly/cli": "^1.25.11", "bump-cli": "^2.8.4" } }, @@ -518,6 +518,7 @@ "version": "1.25.11", "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-1.25.11.tgz", "integrity": "sha512-dttBsmLnnbTlJCTa+s7Sy+qtXDq692n7Ru3nUUIHp9XdCbhXIHWhpc8uAl+GmR4MGbVe8ohATl3J+zX3aFy82A==", + "license": "MIT", "dependencies": { "@redocly/openapi-core": "1.25.11", "abort-controller": "^3.0.0", diff --git a/oas_docs/package.json b/oas_docs/package.json index d007a9881acad..3f6cae5c044b3 100644 --- a/oas_docs/package.json +++ b/oas_docs/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "bump-cli": "^2.8.4", - "@redocly/cli": "^1.25.7" + "@redocly/cli": "^1.25.11" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" From 2296a791790bd4fa0b5dd5d22181aa3549af941c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Fri, 15 Nov 2024 02:16:35 +0100 Subject: [PATCH 30/33] =?UTF-8?q?Prefix=20inference=20endpoint=20with=20?= =?UTF-8?q?=E2=80=9Cobs=5F=E2=80=9D=20(#200174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to https://github.com/elastic/kibana/pull/186499 --- .../server/service/inference_endpoint.ts | 2 +- .../tests/knowledge_base/knowledge_base_setup.spec.ts | 2 +- .../tests/knowledge_base/knowledge_base_status.spec.ts | 2 +- .../tests/knowledge_base/knowledge_base_setup.spec.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/inference_endpoint.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/inference_endpoint.ts index 1d09311dbd6ea..e89028652d9ac 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/inference_endpoint.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/inference_endpoint.ts @@ -10,7 +10,7 @@ import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; import moment from 'moment'; -export const AI_ASSISTANT_KB_INFERENCE_ID = 'ai_assistant_kb_inference'; +export const AI_ASSISTANT_KB_INFERENCE_ID = 'obs_ai_assistant_kb_inference'; export async function createInferenceEndpoint({ esClient, diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts index b8cacaaa58351..7903f4b53966a 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -34,7 +34,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .expect(200); expect(res.body.service_settings.model_id).to.be('pt_tiny_elser'); - expect(res.body.inference_id).to.be('ai_assistant_kb_inference'); + expect(res.body.inference_id).to.be('obs_ai_assistant_kb_inference'); await deleteKnowledgeBaseModel(ml); await deleteInferenceEndpoint({ es }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts index 76ad2d06e344a..8c10a6128d302 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts @@ -77,7 +77,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(res.body.ready).to.be(false); expect(res.body.enabled).to.be(true); expect(res.body.errorMessage).to.include.string( - 'Inference endpoint not found [ai_assistant_kb_inference]' + 'Inference endpoint not found [obs_ai_assistant_kb_inference]' ); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts index a792b01b0e2cb..eeef06464c9d6 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -56,7 +56,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .expect(200); expect(res.body.service_settings.model_id).to.be('pt_tiny_elser'); - expect(res.body.inference_id).to.be('ai_assistant_kb_inference'); + expect(res.body.inference_id).to.be('obs_ai_assistant_kb_inference'); await deleteKnowledgeBaseModel(ml); await deleteInferenceEndpoint({ es }); From d2f07b3a063cffffb846b494d2cb3dd4b918fa20 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 21:18:10 -0600 Subject: [PATCH 31/33] Update ftr (main) (#199090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | Pending | |---|---|---|---|---| | [@types/selenium-webdriver](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/selenium-webdriver) ([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/selenium-webdriver)) | devDependencies | patch | [`^4.1.26` -> `^4.1.27`](https://renovatebot.com/diffs/npm/@types%2fselenium-webdriver/4.1.26/4.1.27) | | | [chromedriver](https://togithub.com/giggio/node-chromedriver) | devDependencies | patch | [`^130.0.1` -> `^130.0.4`](https://renovatebot.com/diffs/npm/chromedriver/130.0.1/130.0.4) | `131.0.0` | | [geckodriver](https://togithub.com/webdriverio-community/node-geckodriver) | devDependencies | major | [`^4.5.1` -> `^5.0.0`](https://renovatebot.com/diffs/npm/geckodriver/4.5.1/5.0.0) | | | [selenium-webdriver](https://togithub.com/SeleniumHQ/selenium/tree/trunk/javascript/node/selenium-webdriver#readme) ([source](https://togithub.com/SeleniumHQ/selenium)) | devDependencies | minor | [`^4.25.0` -> `^4.26.0`](https://renovatebot.com/diffs/npm/selenium-webdriver/4.25.0/4.26.0) | | --- ### Release Notes
giggio/node-chromedriver (chromedriver) ### [`v130.0.4`](https://togithub.com/giggio/node-chromedriver/compare/130.0.3...130.0.4) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/130.0.3...130.0.4) ### [`v130.0.3`](https://togithub.com/giggio/node-chromedriver/compare/130.0.2...130.0.3) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/130.0.2...130.0.3) ### [`v130.0.2`](https://togithub.com/giggio/node-chromedriver/compare/130.0.1...130.0.2) [Compare Source](https://togithub.com/giggio/node-chromedriver/compare/130.0.1...130.0.2)
webdriverio-community/node-geckodriver (geckodriver) ### [`v5.0.0`](https://togithub.com/webdriverio-community/node-geckodriver/releases/tag/v5.0.0): Release 5.0.0 [Compare Source](https://togithub.com/webdriverio-community/node-geckodriver/compare/v4.5.1...v5.0.0) #### ๐Ÿ’ฅ Breaking - update license from MLP to MIT ([`9ecf5e2`](https://togithub.com/webdriverio-community/node-geckodriver/commit/9ecf5e2)) - require Node.js v18 ([`366cbb6`](https://togithub.com/webdriverio-community/node-geckodriver/commit/366cbb6)) #### ๐Ÿงน Chore - set gitattributes ([`5f26dc7`](https://togithub.com/webdriverio-community/node-geckodriver/commit/5f26dc7)) - fix husky ([`29d87c5`](https://togithub.com/webdriverio-community/node-geckodriver/commit/29d87c5)) - update various dependencies
SeleniumHQ/selenium (selenium-webdriver) ### [`v4.26.0`](https://togithub.com/SeleniumHQ/selenium/compare/8a8aea2337281d1824e9fa6b784fc269b59d768e...8ccf0219d77a3144923d79e76e2b8fddd85b3e8f) [Compare Source](https://togithub.com/SeleniumHQ/selenium/compare/8a8aea2337281d1824e9fa6b784fc269b59d768e...8ccf0219d77a3144923d79e76e2b8fddd85b3e8f)
--- ### Configuration ๐Ÿ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). ๐Ÿšฆ **Automerge**: Disabled by config. Please merge this manually once you are satisfied. โ™ป **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. ๐Ÿ‘ป **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://togithub.com/renovatebot/renovate). Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Co-authored-by: Brad White --- package.json | 8 +++--- yarn.lock | 72 ++++++++++++++++++++++++++-------------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index aead0ae928319..acb1a118284ed 100644 --- a/package.json +++ b/package.json @@ -1633,7 +1633,7 @@ "@types/resolve": "^1.20.1", "@types/scheduler": "^0.23.0", "@types/seedrandom": ">=2.0.0 <4.0.0", - "@types/selenium-webdriver": "^4.1.26", + "@types/selenium-webdriver": "^4.1.27", "@types/semver": "^7.5.8", "@types/set-value": "^2.0.0", "@types/sinon": "^7.0.13", @@ -1680,7 +1680,7 @@ "buildkite-test-collector": "^1.7.0", "callsites": "^3.1.0", "chance": "1.0.18", - "chromedriver": "^130.0.1", + "chromedriver": "^130.0.4", "clarify": "^2.2.0", "clean-webpack-plugin": "^3.0.0", "cli-progress": "^3.12.0", @@ -1731,7 +1731,7 @@ "file-loader": "^4.2.0", "find-cypress-specs": "^1.41.4", "form-data": "^4.0.0", - "geckodriver": "^4.5.1", + "geckodriver": "^5.0.0", "gulp-brotli": "^3.0.0", "gulp-postcss": "^9.0.1", "gulp-terser": "^2.1.0", @@ -1806,7 +1806,7 @@ "rxjs-marbles": "^7.0.1", "sass-embedded": "^1.78.0", "sass-loader": "^10.5.1", - "selenium-webdriver": "^4.25.0", + "selenium-webdriver": "^4.26.0", "sharp": "0.32.6", "simple-git": "^3.16.0", "sinon": "^7.4.2", diff --git a/yarn.lock b/yarn.lock index aa8bd6d14de9f..b69c7755172e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1394,10 +1394,10 @@ resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.16.2.tgz#05dd7f06659759fda30f87b15534f1e42f1201bb" integrity sha512-KgqAWMH0emL6f3xH6nqyTryoBMqlJ627LBIe9PT1PRRQPz2FtHib3FIHJPukp1slzF3hJYZvdiVwgPnHbaSOOA== -"@bazel/runfiles@^5.8.1": - version "5.8.1" - resolved "https://registry.yarnpkg.com/@bazel/runfiles/-/runfiles-5.8.1.tgz#737d5b3dc9739767054820265cfe432a80564c82" - integrity sha512-NDdfpdQ6rZlylgv++iMn5FkObC/QlBQvipinGLSOguTYpRywmieOyJ29XHvUilspwTFSILWpoE9CqMGkHXug1g== +"@bazel/runfiles@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@bazel/runfiles/-/runfiles-6.3.1.tgz#3f8824b2d82853377799d42354b4df78ab0ace0b" + integrity sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA== "@bazel/typescript@4.6.2": version "4.6.2" @@ -11334,10 +11334,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/selenium-webdriver@^4.1.26": - version "4.1.26" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.26.tgz#09c696a341cf8cfc1641cded11d14813350b6ca9" - integrity sha512-PUgqsyNffal0eAU0bzGlh37MJo558aporAPZoKqBeB/pF7zhKl1S3zqza0GpwFqgoigNxWhEIJzru75eeYco/w== +"@types/selenium-webdriver@^4.1.27": + version "4.1.27" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.1.27.tgz#e08000d649df6f099b4099432bd2fece9f50ea7b" + integrity sha512-ALqsj8D7Swb6MnBQuAQ58J3KC3yh6fLGtAmpBmnZX8j+0kmP7NaLt56CuzBw2W2bXPrvHFTgn8iekOQFUKXEQA== dependencies: "@types/node" "*" "@types/ws" "*" @@ -11767,10 +11767,10 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@wdio/logger@^9.0.0": - version "9.0.4" - resolved "https://registry.yarnpkg.com/@wdio/logger/-/logger-9.0.4.tgz#63e901b9f0f29fa1ded5af54006fbd4df2354c33" - integrity sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw== +"@wdio/logger@^9.1.3": + version "9.1.3" + resolved "https://registry.yarnpkg.com/@wdio/logger/-/logger-9.1.3.tgz#b64b3d2ac642498f3c97580e2f0971f13c1e8fbb" + integrity sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw== dependencies: chalk "^5.1.2" loglevel "^1.6.0" @@ -12150,10 +12150,10 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== -"@zip.js/zip.js@^2.7.48": - version "2.7.51" - resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.7.51.tgz#a434e285048b951a5788d3d2d59aa68f209e7141" - integrity sha512-RKHaebzZZgQkUuzb49/qweN69e8Np9AUZ9QygydDIrbG1njypSAKwkeqIVeuf2JVGBDyB7Z9HKvzPgYrSlv9gw== +"@zip.js/zip.js@^2.7.53": + version "2.7.53" + resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.7.53.tgz#bf88e90d8eed562182c01339643bc405446b0578" + integrity sha512-G6Bl5wN9EXXVaTUIox71vIX5Z454zEBe+akKpV4m1tUboIctT5h7ID3QXCJd/Lfy2rSvmkTmZIucf1jGRR4f5A== a-sync-waterfall@^1.0.0: version "1.0.1" @@ -14218,10 +14218,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^130.0.1: - version "130.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-130.0.1.tgz#24fd5b2c9c9df4ebfc5d28c94eca8658915fbe15" - integrity sha512-JH+OxDZ7gVv02r9oXwj4mQ8JCtj62g0fCD1LMUUYdB/4mPxn/E2ys+1IzXItoE7vXM9fGVc9R1akvXLqwwuSww== +chromedriver@^130.0.4: + version "130.0.4" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-130.0.4.tgz#55225ddfec428e306116507651f5a24fdb299bd6" + integrity sha512-lpR+PWXszij1k4Ig3t338Zvll9HtCTiwoLM7n4pCCswALHxzmgwaaIFBh3rt9+5wRk9D07oFblrazrBxwaYYAQ== dependencies: "@testim/chrome-version" "^1.1.4" axios "^1.7.4" @@ -18603,19 +18603,19 @@ gcp-metadata@^6.1.0: gaxios "^6.0.0" json-bigint "^1.0.0" -geckodriver@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-4.5.1.tgz#624fc01815c1aa498dd3f717f7bd4c6cca0c57b8" - integrity sha512-lGCRqPMuzbRNDWJOQcUqhNqPvNsIFu6yzXF8J/6K3WCYFd2r5ckbeF7h1cxsnjA7YLSEiWzERCt6/gjZ3tW0ug== +geckodriver@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-5.0.0.tgz#88437f3812075988bb05b5e19dc4aaa42d200577" + integrity sha512-vn7TtQ3b9VMJtVXsyWtQQl1fyBVFhQy7UvJF96kPuuJ0or5THH496AD3eUyaDD11+EqCxH9t6V+EP9soZQk4YQ== dependencies: - "@wdio/logger" "^9.0.0" - "@zip.js/zip.js" "^2.7.48" + "@wdio/logger" "^9.1.3" + "@zip.js/zip.js" "^2.7.53" decamelize "^6.0.0" http-proxy-agent "^7.0.2" https-proxy-agent "^7.0.5" node-fetch "^3.3.2" tar-fs "^3.0.6" - which "^4.0.0" + which "^5.0.0" gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" @@ -28129,12 +28129,12 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-webdriver@^4.25.0: - version "4.25.0" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.25.0.tgz#3562b49668817974bb1d13d25a50e8bc0264fcf3" - integrity sha512-zl9IX93caOT8wbcCpZzAkEtYa+hNgJ4C5GUN8uhpzggqRLvsg1asfKi0p1uNZC8buYVvsBZbx8S+9MjVAjs4oA== +selenium-webdriver@^4.26.0: + version "4.26.0" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.26.0.tgz#23163cdad20388214a4ad17c1f38262a0857c902" + integrity sha512-nA7jMRIPV17mJmAiTDBWN96Sy0Uxrz5CCLb7bLVV6PpL417SyBMPc2Zo/uoREc2EOHlzHwHwAlFtgmSngSY4WQ== dependencies: - "@bazel/runfiles" "^5.8.1" + "@bazel/runfiles" "^6.3.1" jszip "^3.10.1" tmp "^0.2.3" ws "^8.18.0" @@ -32174,10 +32174,10 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -which@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a" - integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg== +which@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6" + integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ== dependencies: isexe "^3.1.1" From 875313e751e70131185459533bd1bfae74efc32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Fri, 15 Nov 2024 06:58:20 +0100 Subject: [PATCH 32/33] Remove `modelId` from config (#200181) Follow up to https://github.com/elastic/kibana/pull/186499 This removes `modelId` from the config file. We don't need it anymore since it can be supplied as a url param. Currently it's only needed in the setup route (`POST /internal/observability_ai_assistant/kb/setup`) --- .../observability_ai_assistant/server/config.ts | 1 - .../observability_ai_assistant/server/plugin.ts | 5 ----- .../configs/index.ts | 2 -- .../test_suites/observability/ai_assistant/config.ts | 4 ---- 4 files changed, 12 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts index 4df8891bd06fc..2f36d27889c14 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts @@ -9,7 +9,6 @@ import { schema, type TypeOf } from '@kbn/config-schema'; export const config = schema.object({ enabled: schema.boolean({ defaultValue: true }), - modelId: schema.maybe(schema.string()), // TODO: Remove scope: schema.maybe(schema.oneOf([schema.literal('observability'), schema.literal('search')])), enableKnowledgeBase: schema.boolean({ defaultValue: true }), }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts index 98a6232563054..b2b5736fd1d6f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts @@ -116,11 +116,6 @@ export class ObservabilityAIAssistantPlugin // Using once to make sure the same model ID is used during service init and Knowledge base setup const getSearchConnectorModelId = once(async () => { - // TODO: Remove this once the modelId is removed from the config - const configModelId = this.config.modelId; - if (configModelId) { - return configModelId; - } const defaultModelId = '.elser_model_2'; const [_, pluginsStart] = await core.getStartServices(); // Wait for the license to be available so the ML plugin's guards pass once we ask for ELSER stats diff --git a/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts b/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts index 4a39c31b0a3a7..75f7bb628b4be 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts @@ -8,7 +8,6 @@ import { mapValues } from 'lodash'; import path from 'path'; import { createTestConfig, CreateTestConfig } from '../common/config'; -import { SUPPORTED_TRAINED_MODELS } from '../../functional/services/ml/api'; export const observabilityAIAssistantDebugLogger = { name: 'plugins.observabilityAIAssistant', @@ -31,7 +30,6 @@ export const observabilityAIAssistantFtrConfigs = { __dirname, '../../../../test/analytics/plugins/analytics_ftr_helpers' ), - 'xpack.observabilityAIAssistant.modelId': SUPPORTED_TRAINED_MODELS.TINY_ELSER.name, // TODO: Remove }, }, }; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/config.ts index 14078f228c7c8..01e470d2a7d88 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/config.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { SUPPORTED_TRAINED_MODELS } from '@kbn/test-suites-xpack/functional/services/ml/api'; import { createTestConfig } from '../../../config.base'; import { ObservabilityAIAssistantServices } from './common/ftr_provider_context'; import { services as inheritedServices } from '../../../services'; @@ -28,7 +27,4 @@ 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: [ - `--xpack.observabilityAIAssistant.modelId=${SUPPORTED_TRAINED_MODELS.TINY_ELSER.name}`, // TODO: Remove - ], }); From 52ba1320599a8d6b6005ab64f562cf9f0a10f2c0 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 15 Nov 2024 10:36:53 +0100 Subject: [PATCH 33/33] [Synthetics] Fixes project monitor decryption filters !! (#199633) ## Summary Fixes project monitor decryption filters , in case of edit, this ends of decrypting all monitors, since search can be pretty wide !! ### Changes - Only fetch relevant previous monitors - Only decrypt relevant monitors - Only fetch relevant previous fields as part of previous monitors --------- Co-authored-by: Dominique Clarke --- .../bulk_cruds/edit_monitor_bulk.ts | 37 +++++------ .../routes/monitor_cruds/edit_monitor.ts | 2 +- .../monitor_cruds/get_monitor_project.ts | 2 +- .../telemetry/monitor_upgrade_sender.test.ts | 2 +- .../telemetry/monitor_upgrade_sender.ts | 9 ++- .../project_monitor_formatter.ts | 66 ++++++++++++------- .../apis/synthetics/get_monitor_project.ts | 15 +++++ 7 files changed, 83 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts index 833e0203e3817..f106dc46c0708 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/bulk_cruds/edit_monitor_bulk.ts @@ -28,7 +28,6 @@ import { export interface MonitorConfigUpdate { normalizedMonitor: SyntheticsMonitor; monitorWithRevision: SyntheticsMonitorWithSecretsAttributes; - previousMonitor: SavedObject; decryptedPreviousMonitor: SavedObject; } @@ -40,14 +39,14 @@ const updateConfigSavedObjects = async ({ monitorsToUpdate: MonitorConfigUpdate[]; }) => { return await routeContext.savedObjectsClient.bulkUpdate( - monitorsToUpdate.map(({ previousMonitor, monitorWithRevision }) => ({ + monitorsToUpdate.map(({ monitorWithRevision, decryptedPreviousMonitor }) => ({ type: syntheticsMonitorType, - id: previousMonitor.id, + id: decryptedPreviousMonitor.id, attributes: { ...monitorWithRevision, - [ConfigKey.CONFIG_ID]: previousMonitor.id, + [ConfigKey.CONFIG_ID]: decryptedPreviousMonitor.id, [ConfigKey.MONITOR_QUERY_ID]: - monitorWithRevision[ConfigKey.CUSTOM_HEARTBEAT_ID] || previousMonitor.id, + monitorWithRevision[ConfigKey.CUSTOM_HEARTBEAT_ID] || decryptedPreviousMonitor.id, }, })) ); @@ -67,15 +66,14 @@ async function syncUpdatedMonitors({ const { syntheticsMonitorClient } = routeContext; return await syntheticsMonitorClient.editMonitors( - monitorsToUpdate.map(({ normalizedMonitor, previousMonitor, decryptedPreviousMonitor }) => ({ + monitorsToUpdate.map(({ normalizedMonitor, decryptedPreviousMonitor }) => ({ monitor: { ...(normalizedMonitor as MonitorFields), - [ConfigKey.CONFIG_ID]: previousMonitor.id, + [ConfigKey.CONFIG_ID]: decryptedPreviousMonitor.id, [ConfigKey.MONITOR_QUERY_ID]: - normalizedMonitor[ConfigKey.CUSTOM_HEARTBEAT_ID] || previousMonitor.id, + normalizedMonitor[ConfigKey.CUSTOM_HEARTBEAT_ID] || decryptedPreviousMonitor.id, }, - id: previousMonitor.id, - previousMonitor, + id: decryptedPreviousMonitor.id, decryptedPreviousMonitor, })), privateLocations, @@ -104,9 +102,9 @@ export const syncEditedMonitorBulk = async ({ const { failedPolicyUpdates, publicSyncErrors } = editSyncResponse; - monitorsToUpdate.forEach(({ normalizedMonitor, previousMonitor }) => { + monitorsToUpdate.forEach(({ normalizedMonitor, decryptedPreviousMonitor }) => { const editedMonitorSavedObject = editedMonitorSavedObjects?.saved_objects.find( - (obj) => obj.id === previousMonitor.id + (obj) => obj.id === decryptedPreviousMonitor.id ); sendTelemetryEvents( @@ -114,7 +112,7 @@ export const syncEditedMonitorBulk = async ({ server.telemetry, formatTelemetryUpdateEvent( editedMonitorSavedObject as SavedObjectsUpdateResponse, - previousMonitor, + decryptedPreviousMonitor.updated_at, server.stackVersion, Boolean((normalizedMonitor as MonitorFields)[ConfigKey.SOURCE_INLINE]), publicSyncErrors @@ -150,9 +148,9 @@ export const rollbackCompletely = async ({ const { savedObjectsClient, server } = routeContext; try { await savedObjectsClient.bulkUpdate( - monitorsToUpdate.map(({ previousMonitor, decryptedPreviousMonitor }) => ({ + monitorsToUpdate.map(({ decryptedPreviousMonitor }) => ({ type: syntheticsMonitorType, - id: previousMonitor.id, + id: decryptedPreviousMonitor.id, attributes: decryptedPreviousMonitor.attributes, })) ); @@ -167,7 +165,6 @@ export const rollbackFailedUpdates = async ({ monitorsToUpdate, }: { monitorsToUpdate: Array<{ - previousMonitor: SavedObject; decryptedPreviousMonitor: SavedObject; }>; routeContext: RouteContext; @@ -194,12 +191,12 @@ export const rollbackFailedUpdates = async ({ }); const monitorsToRevert = monitorsToUpdate - .filter(({ previousMonitor }) => { - return failedConfigs[previousMonitor.id]; + .filter(({ decryptedPreviousMonitor }) => { + return failedConfigs[decryptedPreviousMonitor.id]; }) - .map(({ previousMonitor, decryptedPreviousMonitor }) => ({ + .map(({ decryptedPreviousMonitor }) => ({ type: syntheticsMonitorType, - id: previousMonitor.id, + id: decryptedPreviousMonitor.id, attributes: decryptedPreviousMonitor.attributes, })); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts index d9261847db1ab..d460b71037950 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/edit_monitor.ts @@ -272,7 +272,7 @@ export const syncEditedMonitor = async ({ server.telemetry, formatTelemetryUpdateEvent( editedMonitorSavedObject as SavedObjectsUpdateResponse, - decryptedPreviousMonitor, + decryptedPreviousMonitor.updated_at, server.stackVersion, Boolean((normalizedMonitor as MonitorFields)[ConfigKey.SOURCE_INLINE]), publicSyncErrors diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor_project.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor_project.ts index b0b4eee392d56..ac55807f9412f 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor_project.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/get_monitor_project.ts @@ -32,7 +32,7 @@ export const getSyntheticsProjectMonitorsRoute: SyntheticsRestApiRouteFactory = } = routeContext; const { projectName } = request.params; - const { per_page: perPage = 500, search_after: searchAfter } = request.query; + const { per_page: perPage = 1000, search_after: searchAfter } = request.query; const decodedProjectName = decodeURI(projectName); const decodedSearchAfter = searchAfter ? decodeURI(searchAfter) : undefined; diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.test.ts index 299ab62f09028..761472bb7fa8c 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.test.ts @@ -152,7 +152,7 @@ describe('monitor upgrade telemetry helpers', () => { it('handles formatting update events', () => { const actual = formatTelemetryUpdateEvent( createTestConfig({}, '2011-10-05T16:48:00.000Z'), - testConfig, + testConfig.updated_at, stackVersion, false, errors diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.ts index f008d00a18536..f000108620f45 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/telemetry/monitor_upgrade_sender.ts @@ -109,23 +109,22 @@ export function formatTelemetryEvent({ export function formatTelemetryUpdateEvent( currentMonitor: SavedObjectsUpdateResponse, - previousMonitor: SavedObject, + previousMonitorUpdatedAt: string | undefined, stackVersion: string, isInlineScript: boolean, errors?: ServiceLocationErrors | null ) { let durationSinceLastUpdated: number = 0; - if (currentMonitor.updated_at && previousMonitor.updated_at) { + if (currentMonitor.updated_at && previousMonitorUpdatedAt) { durationSinceLastUpdated = - new Date(currentMonitor.updated_at).getTime() - - new Date(previousMonitor.updated_at).getTime(); + new Date(currentMonitor.updated_at).getTime() - new Date(previousMonitorUpdatedAt).getTime(); } return formatTelemetryEvent({ stackVersion, monitor: currentMonitor as SavedObject, durationSinceLastUpdated, - lastUpdatedAt: previousMonitor.updated_at, + lastUpdatedAt: previousMonitorUpdatedAt, isInlineScript, errors, }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts index 6069076e375a2..1fe5d30048418 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.ts @@ -45,6 +45,17 @@ import { normalizeProjectMonitor } from './normalizers'; type FailedError = Array<{ id?: string; reason: string; details: string; payload?: object }>; +export interface ExistingMonitor { + [ConfigKey.JOURNEY_ID]: string; + [ConfigKey.CONFIG_ID]: string; + [ConfigKey.REVISION]: number; + [ConfigKey.MONITOR_TYPE]: string; +} + +export interface PreviousMonitorForUpdate extends ExistingMonitor { + updated_at?: string; +} + export const CANNOT_UPDATE_MONITOR_TO_DIFFERENT_TYPE = i18n.translate( 'xpack.synthetics.service.projectMonitors.cannotUpdateMonitorToDifferentType', { @@ -128,14 +139,13 @@ export class ProjectMonitorFormatter { const normalizedNewMonitors: SyntheticsMonitor[] = []; const normalizedUpdateMonitors: Array<{ - previousMonitor: SavedObjectsFindResult; + previousMonitor: PreviousMonitorForUpdate; monitor: SyntheticsMonitor; }> = []; for (const monitor of this.monitors) { const previousMonitor = existingMonitors.find( - (monitorObj) => - (monitorObj.attributes as SyntheticsMonitor)[ConfigKey.JOURNEY_ID] === monitor.id + (monitorObj) => monitorObj[ConfigKey.JOURNEY_ID] === monitor.id ); const normM = await this.validateProjectMonitor({ @@ -146,7 +156,7 @@ export class ProjectMonitorFormatter { if (normM) { if ( previousMonitor && - previousMonitor.attributes[ConfigKey.MONITOR_TYPE] !== normM[ConfigKey.MONITOR_TYPE] + previousMonitor[ConfigKey.MONITOR_TYPE] !== normM[ConfigKey.MONITOR_TYPE] ) { this.failedMonitors.push({ reason: CANNOT_UPDATE_MONITOR_TO_DIFFERENT_TYPE, @@ -157,7 +167,7 @@ export class ProjectMonitorFormatter { 'Monitor {monitorId} of type {previousType} cannot be updated to type {currentType}. Please delete the monitor first and try again.', values: { currentType: monitor.type, - previousType: previousMonitor.attributes[ConfigKey.MONITOR_TYPE], + previousType: previousMonitor[ConfigKey.MONITOR_TYPE], monitorId: monitor.id, }, } @@ -246,19 +256,33 @@ export class ProjectMonitorFormatter { } }; - public getProjectMonitorsForProject = async () => { - const finder = this.savedObjectsClient.createPointInTimeFinder({ + public getProjectMonitorsForProject = async (): Promise => { + const journeyIds = this.monitors.map((monitor) => monitor.id); + const journeyFilter = getSavedObjectKqlFilter({ + field: ConfigKey.JOURNEY_ID, + values: journeyIds, + }); + const finder = this.savedObjectsClient.createPointInTimeFinder({ type: syntheticsMonitorType, perPage: 5000, - filter: this.projectFilter, + filter: `${this.projectFilter} AND ${journeyFilter}`, + fields: [ + ConfigKey.JOURNEY_ID, + ConfigKey.CONFIG_ID, + ConfigKey.REVISION, + ConfigKey.MONITOR_TYPE, + ], }); - const hits: Array> = []; + const hits: PreviousMonitorForUpdate[] = []; for await (const result of finder.find()) { hits.push( - ...(result.saved_objects as Array< - SavedObjectsFindResult - >) + ...result.saved_objects.map((monitor) => { + return { + ...monitor.attributes, + updated_at: monitor.updated_at, + }; + }) ); } @@ -333,10 +357,8 @@ export class ProjectMonitorFormatter { } }; - private getDecryptedMonitors = async ( - monitors: Array> - ) => { - const configIds = monitors.map((monitor) => monitor.attributes[ConfigKey.CONFIG_ID]); + private getDecryptedMonitors = async (monitors: PreviousMonitorForUpdate[]) => { + const configIds = monitors.map((monitor) => monitor[ConfigKey.CONFIG_ID]); const monitorFilter = getSavedObjectKqlFilter({ field: ConfigKey.CONFIG_ID, values: configIds, @@ -344,7 +366,7 @@ export class ProjectMonitorFormatter { const finder = await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( { - search: monitorFilter, + filter: monitorFilter, type: syntheticsMonitorType, perPage: 500, namespaces: [this.spaceId], @@ -365,7 +387,7 @@ export class ProjectMonitorFormatter { private updateMonitorsBulk = async ( monitors: Array<{ monitor: SyntheticsMonitor; - previousMonitor: SavedObjectsFindResult; + previousMonitor: PreviousMonitorForUpdate; }> ): Promise< | { @@ -390,10 +412,11 @@ export class ProjectMonitorFormatter { const monitorsToUpdate: MonitorConfigUpdate[] = []; decryptedPreviousMonitors.forEach((decryptedPreviousMonitor) => { - const monitor = monitors.find((m) => m.previousMonitor.id === decryptedPreviousMonitor.id); + const monitor = monitors.find( + (m) => m.previousMonitor[ConfigKey.CONFIG_ID] === decryptedPreviousMonitor.id + ); if (monitor) { const normalizedMonitor = monitor?.monitor; - const previousMonitor = monitor?.previousMonitor; const { attributes: { [ConfigKey.REVISION]: _, ...normalizedPrevMonitorAttr }, } = normalizeSecrets(decryptedPreviousMonitor); @@ -401,11 +424,10 @@ export class ProjectMonitorFormatter { const monitorWithRevision = formatSecrets({ ...normalizedPrevMonitorAttr, ...normalizedMonitor, - revision: (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, + revision: (decryptedPreviousMonitor.attributes[ConfigKey.REVISION] || 0) + 1, }); monitorsToUpdate.push({ normalizedMonitor, - previousMonitor, monitorWithRevision, decryptedPreviousMonitor, }); diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts index 53089d2bec2d3..dd3fcbef3088d 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_project.ts @@ -95,6 +95,9 @@ export default function ({ getService }: FtrProviderContext) { const firstPageResponse = await supertest .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) + .query({ + per_page: 500, + }) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -185,6 +188,9 @@ export default function ({ getService }: FtrProviderContext) { const firstPageResponse = await supertest .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) + .query({ + per_page: 500, + }) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -279,6 +285,9 @@ export default function ({ getService }: FtrProviderContext) { const firstPageResponse = await supertest .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) + .query({ + per_page: 500, + }) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -372,6 +381,9 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); const firstPageResponse = await supertest .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS_PROJECT.replace('{projectName}', project)) + .query({ + per_page: 500, + }) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -481,6 +493,9 @@ export default function ({ getService }: FtrProviderContext) { encodeURI(projectName) ) ) + .query({ + per_page: 500, + }) .set('kbn-xsrf', 'true') .send() .expect(200);