From 761b17075aa65eac964c32985d19137544412992 Mon Sep 17 00:00:00 2001 From: Tre Date: Wed, 6 Nov 2024 11:50:37 +0000 Subject: [PATCH 01/53] [SKIP ON MKI] `x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts` (#199094) See details: https://github.com/elastic/kibana/issues/199091 --------- Co-authored-by: Robert Oskamp Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../ftr/cloud_security_posture/agentless_api/create_agent.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts index b26581fb46dfd..6d0686e189572 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless_api/create_agent.ts @@ -25,6 +25,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const AWS_SINGLE_ACCOUNT_TEST_ID = 'awsSingleTestId'; describe('Agentless API Serverless', function () { + // see details: https://github.com/elastic/kibana/issues/199091 + this.tags(['failsOnMKI']); let mockApiServer: http.Server; let cisIntegration: typeof pageObjects.cisAddIntegration; From 436405fefbbd834a68df93d8179a5b377e84b614 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 6 Nov 2024 13:00:49 +0100 Subject: [PATCH 02/53] [Discover][UnifiedDataTable] Enable drag&drop for grid columns (#197832) - Closes https://github.com/elastic/kibana/issues/195769 ## Summary Eui now supports reordering of grid columns by dra&drop https://github.com/elastic/eui/pull/8015 The PR enables this functionality for UnifiedDataTable. ![Nov-01-2024 10-21-49](https://github.com/user-attachments/assets/bc47507c-7b9e-44c2-88d7-5f48f37924cb) ### Checklist - [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 --- .../data_table_columns.test.tsx.snap | 45 +++++++++++++++++++ .../src/components/data_table.scss | 22 +++++---- .../src/components/data_table.test.tsx | 44 ++++++++++++++++++ .../src/components/data_table.tsx | 14 +++++- .../src/components/data_table_columns.tsx | 1 + .../discover_grid/discover_grid.tsx | 1 + .../_data_grid_column_widths.ts | 4 ++ test/functional/services/data_grid.ts | 35 +++++++++++++-- 8 files changed, 154 insertions(+), 12 deletions(-) diff --git a/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap b/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap index bd28bfc354f9f..fd1ad71558aa5 100644 --- a/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap +++ b/packages/kbn-unified-data-table/src/components/__snapshots__/data_table_columns.test.tsx.snap @@ -48,6 +48,9 @@ Array [ test , "displayAsText": "extension", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "extension", "isSortable": true, "schema": "string", @@ -332,6 +335,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "message", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "message", "isSortable": true, "schema": "string", @@ -589,6 +595,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "timestamp", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "timestamp", "initialWidth": 212, "isSortable": true, @@ -837,6 +846,9 @@ Array [ showColumnTokens={true} />, "displayAsText": "extension", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "extension", "isSortable": false, "schema": "string", @@ -1082,6 +1094,9 @@ Array [ showColumnTokens={true} />, "displayAsText": "message", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "message", "isSortable": false, "schema": "string", @@ -1372,6 +1387,9 @@ Array [ showColumnTokens={true} />, "displayAsText": "extension", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "extension", "isSortable": false, "schema": "string", @@ -1655,6 +1673,9 @@ Array [ showColumnTokens={true} />, "displayAsText": "message", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "message", "isSortable": false, "schema": "string", @@ -1819,6 +1840,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "extension", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "extension", "isSortable": false, "schema": "string", @@ -1976,6 +2000,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "message", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "message", "isSortable": false, "schema": "string", @@ -2191,6 +2218,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "timestamp", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "timestamp", "initialWidth": 212, "isSortable": true, @@ -2396,6 +2426,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "extension", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "extension", "isSortable": false, "schema": "string", @@ -2598,6 +2631,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "message", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "message", "isSortable": false, "schema": "string", @@ -2823,6 +2859,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "timestamp", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "timestamp", "initialWidth": 212, "isSortable": true, @@ -3043,6 +3082,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "extension", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "extension", "isSortable": true, "schema": "string", @@ -3262,6 +3304,9 @@ Array [ headerRowHeight={5} />, "displayAsText": "message", + "displayHeaderCellProps": Object { + "className": "unifiedDataTable__headerCell", + }, "id": "message", "isSortable": true, "schema": "string", diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index 6093659d487d6..f530e870665f8 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -40,14 +40,6 @@ background: transparent; } - .euiDataGridHeaderCell { - align-items: start; - - .euiPopover[class*='euiDataGridHeaderCell__popover'] { - align-self: center; - } - } - .euiDataGrid--bordersHorizontal .euiDataGridHeader { border-top: none; } @@ -101,6 +93,20 @@ } } +// Custom styles for data grid header cell. +// It can also be inside a portal (outside of `unifiedDataTable__inner`) when dragged. +.unifiedDataTable__headerCell { + align-items: start; + + .euiDataGridHeaderCell__draggableIcon { + padding-block: $euiSizeXS / 2; // to align with a token height + } + + .euiDataGridHeaderCell__button { + margin-block: -$euiSizeXS; // to override Eui value for Density "Expanded" + } +} + .unifiedDataTable__table { flex-grow: 1; flex-shrink: 1; diff --git a/packages/kbn-unified-data-table/src/components/data_table.test.tsx b/packages/kbn-unified-data-table/src/components/data_table.test.tsx index 13304d4661cc0..f440c2845adaa 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.test.tsx @@ -1354,5 +1354,49 @@ describe('UnifiedDataTable', () => { }, EXTENDED_JEST_TIMEOUT ); + + it( + 'should have columnVisibility configuration', + async () => { + const component = await getComponent({ + ...getProps(), + columns: ['message'], + canDragAndDropColumns: true, + }); + expect(component.find(EuiDataGrid).last().prop('columnVisibility')).toMatchInlineSnapshot(` + Object { + "canDragAndDropColumns": true, + "setVisibleColumns": [Function], + "visibleColumns": Array [ + "@timestamp", + "message", + ], + } + `); + }, + EXTENDED_JEST_TIMEOUT + ); + + it( + 'should disable drag&drop if Summary is present', + async () => { + const component = await getComponent({ + ...getProps(), + columns: [], + canDragAndDropColumns: true, + }); + expect(component.find(EuiDataGrid).last().prop('columnVisibility')).toMatchInlineSnapshot(` + Object { + "canDragAndDropColumns": false, + "setVisibleColumns": [Function], + "visibleColumns": Array [ + "@timestamp", + "_source", + ], + } + `); + }, + EXTENDED_JEST_TIMEOUT + ); }); }); diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index 662c8526dd567..a22ee8317be2f 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -137,6 +137,10 @@ export interface UnifiedDataTableProps { * Field tokens could be rendered in column header next to the field name. */ showColumnTokens?: boolean; + /** + * Set to true to allow users to drag and drop columns for reordering + */ + canDragAndDropColumns?: boolean; /** * Optional value for providing configuration setting for UnifiedDataTable header row height */ @@ -425,6 +429,7 @@ export const UnifiedDataTable = ({ columns, columnsMeta, showColumnTokens, + canDragAndDropColumns, configHeaderRowHeight, headerRowHeightState, onUpdateHeaderRowHeight, @@ -870,13 +875,20 @@ export const UnifiedDataTable = ({ const schemaDetectors = useMemo(() => getSchemaDetectors(), []); const columnsVisibility = useMemo( () => ({ + canDragAndDropColumns: defaultColumns ? false : canDragAndDropColumns, visibleColumns, setVisibleColumns: (newColumns: string[]) => { const dontModifyColumns = !shouldPrependTimeFieldColumn(newColumns); onSetColumns(newColumns, dontModifyColumns); }, }), - [visibleColumns, onSetColumns, shouldPrependTimeFieldColumn] + [ + visibleColumns, + onSetColumns, + shouldPrependTimeFieldColumn, + canDragAndDropColumns, + defaultColumns, + ] ); const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView); diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx index 985a5db9f2178..8f1503ade8a7c 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -258,6 +258,7 @@ function buildEuiGridColumn({ }, cellActions, visibleCellActions, + displayHeaderCellProps: { className: 'unifiedDataTable__headerCell' }, }; if (column.id === dataView.timeFieldName) { diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 3b0a2df2582aa..e909d3e256d22 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -58,6 +58,7 @@ export const DiscoverGrid: React.FC = ({ return ( { await unifiedFieldList.clickFieldListItemAdd('@message'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); expect(await dataGrid.resetColumnWidthExists('@message')).to.be(false); }); it('should show reset width button for absolute width column, and allow resetting to auto width', async () => { await unifiedFieldList.clickFieldListItemAdd('@message'); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); await testResizeColumn('@message'); }); diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 07a723857088c..f56b58cfa88f1 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -102,9 +102,38 @@ export class DataGridService extends FtrService { public async resizeColumn(field: string, delta: number) { const header = await this.getHeaderElement(field); const originalWidth = (await header.getSize()).width; - const resizer = await header.findByCssSelector( - this.testSubjects.getCssSelector('dataGridColumnResizer') - ); + + let resizer: WebElementWrapper | undefined; + + if (await this.testSubjects.exists('euiDataGridHeaderDroppable')) { + // if drag & drop is enabled for data grid columns + const headerDraggableColumns = await this.find.allByCssSelector( + '[data-test-subj="euiDataGridHeaderDroppable"] > div' + ); + // searching for a common parent of the field column header and its resizer + const fieldHeader: WebElementWrapper | null | undefined = ( + await Promise.all( + headerDraggableColumns.map(async (column) => { + const hasFieldColumn = + (await column.findAllByCssSelector(`[data-gridcell-column-id="${field}"]`)).length > + 0; + return hasFieldColumn ? column : null; + }) + ) + ).find(Boolean); + + resizer = await fieldHeader?.findByTestSubject('dataGridColumnResizer'); + } else { + // if drag & drop is not enabled for data grid columns + resizer = await header.findByCssSelector( + this.testSubjects.getCssSelector('dataGridColumnResizer') + ); + } + + if (!resizer) { + throw new Error(`Unable to find column resizer for field ${field}`); + } + await this.browser.dragAndDrop({ location: resizer }, { location: { x: delta, y: 0 } }); return { originalWidth, newWidth: (await header.getSize()).width }; } From b585ca658aafeac2d83b5cd2b3a3c2c917c90d01 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Wed, 6 Nov 2024 13:03:21 +0100 Subject: [PATCH 03/53] Migrate Custom threshold duplicated tests to the deployment agnostic framework (#198691) Closes #183378 Closes #179095 ## Summary This PR moves the Custom threshold rule duplicated API integration tests to the deployment agnostic test. ## How to run To run 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="Custom Threshold rule" ``` To run 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="Custom Threshold rule" ``` ### TODO - [x] Test in MKI before merging #### How to run tests on MKI According to this [discussion](https://github.com/elastic/observability-dev/issues/3519#issuecomment-2379914274), we should test in MKI environment before merging. For details on how to run in MKI, see [this section of the document](https://docs.google.com/document/d/1tiax7xoDYwFXYZjRTgVKkVMjN-SQzBWk4yn1JY6Z5UY/edit#heading=h.ece2z8p74izh) and [this readme](https://github.com/elastic/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki). --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../custom_threshold_rule/avg_pct_no_data.ts | 240 --------------- .../custom_threshold_rule/constants.ts | 8 - .../custom_eq_avg_bytes_fired.ts | 257 ---------------- .../documents_count_fired.ts | 273 ----------------- .../custom_threshold_rule/group_by_fired.ts | 287 ------------------ .../custom_threshold_rule/p99_pct_fired.ts | 266 ---------------- .../custom_threshold_rule/typings.ts | 25 -- .../observability/index.ts | 7 - .../observability/alerting/burn_rate_rule.ts | 14 + .../custom_threshold/avg_pct_fired.ts | 4 +- .../custom_threshold}/avg_pct_no_data.ts | 48 +-- .../custom_threshold}/avg_us_fired.ts | 106 +++---- .../custom_eq_avg_bytes_fired.ts | 44 +-- .../documents_count_fired.ts | 46 +-- .../custom_threshold}/group_by_fired.ts | 44 +-- .../custom_threshold/helpers/syntrace.ts | 159 ++++++++++ .../alerting/custom_threshold/index.ts | 7 + .../custom_threshold}/p99_pct_fired.ts | 42 +-- .../custom_threshold}/rate_bytes_fired.ts | 100 +++--- .../observability/alerting/es_query_rule.ts | 7 +- .../custom_threshold_rule/constants.ts | 8 - .../custom_threshold_rule/index.ts | 18 -- .../custom_threshold_rule/typings.ts | 25 -- .../observability/index.feature_flags.ts | 1 - x-pack/test_serverless/tsconfig.json | 5 - 25 files changed, 422 insertions(+), 1619 deletions(-) delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/constants.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts delete mode 100644 x-pack/test/alerting_api_integration/observability/custom_threshold_rule/typings.ts rename x-pack/{test_serverless/api_integration/test_suites/observability/custom_threshold_rule => test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/avg_pct_no_data.ts (86%) rename x-pack/test/{alerting_api_integration/observability/custom_threshold_rule => api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/avg_us_fired.ts (74%) rename x-pack/{test_serverless/api_integration/test_suites/observability/custom_threshold_rule => test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/custom_eq_avg_bytes_fired.ts (87%) rename x-pack/{test_serverless/api_integration/test_suites/observability/custom_threshold_rule => test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/documents_count_fired.ts (88%) rename x-pack/{test_serverless/api_integration/test_suites/observability/custom_threshold_rule => test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/group_by_fired.ts (89%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/helpers/syntrace.ts rename x-pack/{test_serverless/api_integration/test_suites/observability/custom_threshold_rule => test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/p99_pct_fired.ts (88%) rename x-pack/test/{alerting_api_integration/observability/custom_threshold_rule => api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold}/rate_bytes_fired.ts (78%) delete mode 100644 x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/constants.ts delete mode 100644 x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts delete mode 100644 x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/typings.ts diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts deleted file mode 100644 index f552eca84b8a4..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts +++ /dev/null @@ -1,240 +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 { omit } from 'lodash'; -import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; -import { NO_DATA_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { COMPARATORS } from '@kbn/alerting-comparators'; - -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ISO_DATE_REGEX } from './constants'; -import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const retryService = getService('retry'); - - describe('Custom Threshold rule - AVG - PCT - NoData', () => { - const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW = 'no-data-pattern'; - const DATA_VIEW_ID = 'data-view-id-no-data'; - let actionId: string; - let ruleId: string; - let alertId: string; - - before(async () => { - await createDataView({ - supertest, - name: DATA_VIEW, - id: DATA_VIEW_ID, - title: DATA_VIEW, - logger, - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - logger, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX]); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - logger, - }); - - const createdRule = await createRule({ - supertest, - logger, - esClient, - tags: ['observability'], - consumer: 'logs', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - comparator: COMPARATORS.GREATER_THAN, - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - metrics: [ - { name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: NO_DATA_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - viewInAppUrl: '{{context.viewInAppUrl}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - retryService, - logger, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - ruleId, - retryService, - logger, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Custom threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.custom_threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.action_group', - 'custom_threshold.nodata' - ); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - comparator: '>', - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - index: 'data-view-id-no-data', - query: { query: '', language: 'kuery' }, - }, - }); - }); - - it('should set correct action variables', async () => { - const resp = await waitForDocumentInIndex({ - esClient, - indexName: ALERT_ACTION_INDEX, - retryService, - logger, - }); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` - ); - expect(resp.hits.hits[0]._source?.reason).eql( - 'Average system.cpu.user.pct reported no data in the last 5m' - ); - expect(resp.hits.hits[0]._source?.value).eql('[NO DATA]'); - - const parsedViewInAppUrl = parseSearchParams( - new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search - ); - - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); - expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_ID, - timeRange: { to: 'now' }, - query: { query: '', language: 'kuery' }, - filters: [], - }); - expect(parsedViewInAppUrl.params.timeRange.from).match(ISO_DATE_REGEX); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/constants.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/constants.ts deleted file mode 100644 index 5cf1e0b4d6614..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/constants.ts +++ /dev/null @@ -1,8 +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. - */ - -export const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts deleted file mode 100644 index 56370d31a38f7..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ /dev/null @@ -1,257 +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 { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; -import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; -import { COMPARATORS } from '@kbn/alerting-comparators'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ActionDocument } from './typings'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const retryService = getService('retry'); - - describe('Custom Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { - const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - // DATA_VIEW should match the index template: - const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; - const DATA_VIEW_ID = 'data-view-id'; - let dataForgeConfig: PartialConfig; - let dataForgeIndices: string[]; - let actionId: string; - let ruleId: string; - let alertId: string; - - before(async () => { - dataForgeConfig = { - schedule: [ - { - template: 'good', - start: 'now-15m', - end: 'now+10m', - metrics: [ - { name: 'system.network.in.bytes', method: 'linear', start: 5, end: 5 }, - { name: 'system.network.out.bytes', method: 'linear', start: 5, end: 5 }, - ], - }, - ], - indexing: { - dataset: 'fake_hosts' as Dataset, - eventsPerCycle: 1, - interval: 60000, - alignEventsToInterval: true, - }, - }; - dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); - await waitForDocumentInIndex({ - esClient, - indexName: DATA_VIEW, - docCountTarget: 75, - retryService, - logger, - }); - await createDataView({ - supertest, - name: DATA_VIEW, - id: DATA_VIEW_ID, - title: DATA_VIEW, - logger, - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - logger, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); - await cleanup({ client: esClient, config: dataForgeConfig, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - logger, - }); - - const createdRule = await createRule({ - supertest, - logger, - esClient, - tags: ['observability'], - consumer: 'logs', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - comparator: COMPARATORS.GREATER_THAN, - threshold: [0.9], - timeSize: 1, - timeUnit: 'm', - metrics: [ - { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, - { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, - ], - equation: '(A + A) / (B + B)', - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - retryService, - logger, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - ruleId, - retryService, - logger, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Custom threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.custom_threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.action_group', - 'custom_threshold.fired' - ); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - expect(resp.hits.hits[0]._source).property('kibana.alert.evaluation.threshold').eql([0.9]); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - comparator: COMPARATORS.GREATER_THAN, - threshold: [0.9], - timeSize: 1, - timeUnit: 'm', - metrics: [ - { name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE }, - { name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE }, - ], - equation: '(A + A) / (B + B)', - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - }); - }); - - it('should set correct action variables', async () => { - const resp = await waitForDocumentInIndex({ - esClient, - indexName: ALERT_ACTION_INDEX, - retryService, - logger, - }); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` - ); - expect(resp.hits.hits[0]._source?.reason).eql( - `Custom equation is 1 B, above the threshold of 0.9 B. (duration: 1 min, data view: ${DATA_VIEW})` - ); - expect(resp.hits.hits[0]._source?.value).eql('1 B'); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts deleted file mode 100644 index 5c56dbdf9e808..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts +++ /dev/null @@ -1,273 +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 { omit } from 'lodash'; -import expect from '@kbn/expect'; -import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; -import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; -import { COMPARATORS } from '@kbn/alerting-comparators'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ISO_DATE_REGEX } from './constants'; -import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const retryService = getService('retry'); - - describe('Custom Threshold rule - DOCUMENTS_COUNT - FIRED', () => { - const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; - const DATA_VIEW_ID = 'data-view-id'; - const DATA_VIEW_NAME = 'data-view-name'; - let dataForgeConfig: PartialConfig; - let dataForgeIndices: string[]; - let actionId: string; - let ruleId: string; - let alertId: string; - - before(async () => { - dataForgeConfig = { - schedule: [ - { - template: 'good', - start: 'now-10m', - end: 'now+5m', - metrics: [ - { name: 'system.cpu.user.pct', method: 'linear', start: 2.5, end: 2.5 }, - { name: 'system.cpu.total.pct', method: 'linear', start: 0.5, end: 0.5 }, - { name: 'system.cpu.total.norm.pct', method: 'linear', start: 0.8, end: 0.8 }, - ], - }, - ], - indexing: { - dataset: 'fake_hosts' as Dataset, - eventsPerCycle: 1, - interval: 60000, - alignEventsToInterval: true, - }, - }; - dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); - await waitForDocumentInIndex({ - esClient, - indexName: dataForgeIndices.join(','), - docCountTarget: 45, - retryService, - logger, - }); - await createDataView({ - supertest, - name: DATA_VIEW_NAME, - id: DATA_VIEW_ID, - title: DATA_VIEW, - logger, - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - logger, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); - await cleanup({ client: esClient, config: dataForgeConfig, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - logger, - }); - - const createdRule = await createRule({ - supertest, - logger, - esClient, - tags: ['observability'], - consumer: 'logs', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - comparator: COMPARATORS.NOT_BETWEEN, - threshold: [1, 2], - timeSize: 1, - timeUnit: 'm', - metrics: [{ name: 'A', filter: 'container.id:*', aggType: Aggregators.COUNT }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: 'host.name:*', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - viewInAppUrl: '{{context.viewInAppUrl}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - retryService, - logger, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - ruleId, - retryService, - logger, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Custom threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.custom_threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.action_group', - 'custom_threshold.fired' - ); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source).not.have.property('kibana.alert.group'); - expect(resp.hits.hits[0]._source).not.have.property('kibana.alert.evaluation.threshold'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - comparator: COMPARATORS.NOT_BETWEEN, - threshold: [1, 2], - timeSize: 1, - timeUnit: 'm', - metrics: [{ name: 'A', filter: 'container.id:*', aggType: 'count' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - index: 'data-view-id', - query: { query: 'host.name:*', language: 'kuery' }, - }, - }); - }); - - it('should set correct action variables', async () => { - const resp = await waitForDocumentInIndex({ - esClient, - indexName: ALERT_ACTION_INDEX, - retryService, - logger, - }); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` - ); - - expect(resp.hits.hits[0]._source?.reason).eql( - `Document count is 3, not between the threshold of 1 and 2. (duration: 1 min, data view: ${DATA_VIEW_NAME})` - ); - expect(resp.hits.hits[0]._source?.value).eql('3'); - - const parsedViewInAppUrl = parseSearchParams( - new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search - ); - - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); - expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_ID, - timeRange: { to: 'now' }, - query: { query: 'host.name:* and container.id:*', language: 'kuery' }, - filters: [], - }); - expect(parsedViewInAppUrl.params.timeRange.from).match(ISO_DATE_REGEX); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts deleted file mode 100644 index 6f4402575ac7c..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts +++ /dev/null @@ -1,287 +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 { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; -import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { COMPARATORS } from '@kbn/alerting-comparators'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ActionDocument } from './typings'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const retryService = getService('retry'); - - describe('Custom Threshold rule - GROUP_BY - FIRED', () => { - const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; - const DATA_VIEW_ID = 'data-view-id'; - let dataForgeConfig: PartialConfig; - let dataForgeIndices: string[]; - let actionId: string; - let ruleId: string; - let alertId: string; - - before(async () => { - dataForgeConfig = { - schedule: [ - { - template: 'good', - start: 'now-10m', - end: 'now+5m', - metrics: [ - { name: 'system.cpu.user.pct', method: 'linear', start: 2.5, end: 2.5 }, - { name: 'system.cpu.total.pct', method: 'linear', start: 0.5, end: 0.5 }, - { name: 'system.cpu.total.norm.pct', method: 'linear', start: 0.8, end: 0.8 }, - ], - }, - ], - indexing: { - dataset: 'fake_hosts' as Dataset, - eventsPerCycle: 1, - interval: 10000, - alignEventsToInterval: true, - }, - }; - dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); - await waitForDocumentInIndex({ - esClient, - indexName: dataForgeIndices.join(','), - docCountTarget: 270, - retryService, - logger, - }); - await createDataView({ - supertest, - name: DATA_VIEW, - id: DATA_VIEW_ID, - title: DATA_VIEW, - logger, - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, - }); - await deleteDataView({ - supertest, - id: DATA_VIEW_ID, - logger, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); - await cleanup({ client: esClient, config: dataForgeConfig, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - logger, - }); - - const createdRule = await createRule({ - supertest, - logger, - esClient, - tags: ['observability'], - consumer: 'logs', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - comparator: COMPARATORS.GREATER_THAN_OR_EQUALS, - threshold: [0.2], - timeSize: 1, - timeUnit: 'm', - metrics: [ - { name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE }, - ], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: DATA_VIEW_ID, - }, - groupBy: ['host.name', 'container.id', 'event.dataset', '_index'], - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - host: '{{context.host}}', - group: '{{context.group}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - retryService, - logger, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - ruleId, - retryService, - logger, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Custom threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.custom_threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.action_group', - 'custom_threshold.fired' - ); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.instance.id') - .contain('host-0,container-0,system.cpu,kbn-data-forge-fake_hosts.fake_hosts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source).property('host.name', 'host-0'); - expect(resp.hits.hits[0]._source) - .property('host.mac') - .eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']); - expect(resp.hits.hits[0]._source).property('container.id', 'container-0'); - expect(resp.hits.hits[0]._source).property('container.name', 'container-name'); - expect(resp.hits.hits[0]._source).property('event.dataset', 'system.cpu'); - expect(resp.hits.hits[0]._source).not.property('container.cpu'); - - const alertGroups = (resp.hits.hits[0]._source as any)?.['kibana.alert.group']; - expect(alertGroups[0]).eql({ - field: 'host.name', - value: 'host-0', - }); - expect(alertGroups[1]).eql({ - field: 'container.id', - value: 'container-0', - }); - expect(alertGroups[2]).eql({ - field: 'event.dataset', - value: 'system.cpu', - }); - expect(alertGroups[3].value).contain('kbn-data-forge-fake_hosts.fake_hosts'); - expect(resp.hits.hits[0]._source).property('kibana.alert.evaluation.threshold').eql([0.2]); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - comparator: '>=', - threshold: [0.2], - timeSize: 1, - timeUnit: 'm', - metrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } }, - groupBy: ['host.name', 'container.id', 'event.dataset', '_index'], - }); - }); - - it('should set correct action variables', async () => { - const resp = await waitForDocumentInIndex({ - esClient, - indexName: ALERT_ACTION_INDEX, - retryService, - logger, - }); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` - ); - expect(resp.hits.hits[0]._source?.reason).contain( - `Average system.cpu.total.norm.pct is 80%, above or equal the threshold of 20%. (duration: 1 min, data view: ${DATA_VIEW}, group: host-0,container-0,system.cpu,kbn-data-forge-fake_hosts.fake_hosts` - ); - expect(resp.hits.hits[0]._source?.value).eql('80%'); - expect(resp.hits.hits[0]._source?.host).eql( - '{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}' - ); - expect(resp.hits.hits[0]._source?.group).contain( - '{"field":"host.name","value":"host-0"},{"field":"container.id","value":"container-0"},{"field":"event.dataset","value":"system.cpu"},{"field":"_index","value":"kbn-data-forge-fake_hosts.fake_hosts' - ); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts deleted file mode 100644 index b76c09c12642d..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/p99_pct_fired.ts +++ /dev/null @@ -1,266 +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 { omit } from 'lodash'; -import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; -import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; -import { COMPARATORS } from '@kbn/alerting-comparators'; -import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; -import { ISO_DATE_REGEX } from './constants'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { - const esClient = getService('es'); - const supertest = getService('supertest'); - const esDeleteAllIndices = getService('esDeleteAllIndices'); - const logger = getService('log'); - const retryService = getService('retry'); - - describe('Custom Threshold rule - P99 - PCT - FIRED', () => { - const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; - const ALERT_ACTION_INDEX = 'alert-action-threshold'; - const DATA_VIEW_TITLE = 'kbn-data-forge-fake_hosts.fake_hosts-*'; - const DATA_VIEW_NAME = 'ad-hoc-data-view-name'; - const DATA_VIEW_ID = 'data-view-id'; - const MOCKED_AD_HOC_DATA_VIEW = { - id: DATA_VIEW_ID, - title: DATA_VIEW_TITLE, - timeFieldName: '@timestamp', - sourceFilters: [], - fieldFormats: {}, - runtimeFieldMap: {}, - allowNoIndex: false, - name: DATA_VIEW_NAME, - allowHidden: false, - }; - let dataForgeConfig: PartialConfig; - let dataForgeIndices: string[]; - let actionId: string; - let ruleId: string; - let alertId: string; - - before(async () => { - dataForgeConfig = { - schedule: [ - { - template: 'good', - start: 'now-10m', - end: 'now+5m', - metrics: [{ name: 'system.cpu.user.pct', method: 'linear', start: 2.5, end: 2.5 }], - }, - ], - indexing: { - dataset: 'fake_hosts' as Dataset, - eventsPerCycle: 1, - interval: 10000, - alignEventsToInterval: true, - }, - }; - dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); - logger.info(JSON.stringify(dataForgeIndices.join(','))); - await waitForDocumentInIndex({ - esClient, - indexName: DATA_VIEW_TITLE, - docCountTarget: 270, - retryService, - logger, - }); - }); - - after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); - await esClient.deleteByQuery({ - index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - query: { term: { 'kibana.alert.rule.uuid': ruleId } }, - }); - await esClient.deleteByQuery({ - index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, - }); - await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); - await cleanup({ client: esClient, config: dataForgeConfig, logger }); - }); - - describe('Rule creation', () => { - it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, - name: 'Index Connector: Threshold API test', - indexName: ALERT_ACTION_INDEX, - logger, - }); - - const createdRule = await createRule({ - supertest, - logger, - esClient, - tags: ['observability'], - consumer: 'logs', - name: 'Threshold rule', - ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, - params: { - criteria: [ - { - comparator: COMPARATORS.GREATER_THAN, - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.P99 }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index: MOCKED_AD_HOC_DATA_VIEW, - }, - }, - actions: [ - { - group: FIRED_ACTIONS_ID, - id: actionId, - params: { - documents: [ - { - ruleType: '{{rule.type}}', - alertDetailsUrl: '{{context.alertDetailsUrl}}', - reason: '{{context.reason}}', - value: '{{context.value}}', - host: '{{context.host}}', - viewInAppUrl: '{{context.viewInAppUrl}}', - }, - ], - }, - frequency: { - notify_when: 'onActionGroupChange', - throttle: null, - summary: false, - }, - }, - ], - }); - ruleId = createdRule.id; - expect(ruleId).not.to.be(undefined); - }); - - it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, - expectedStatus: 'active', - supertest, - retryService, - logger, - }); - expect(executionStatus.status).to.be('active'); - }); - - it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, - indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, - ruleId, - retryService, - logger, - }); - alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; - - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.category', - 'Custom threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.rule.rule_type_id', - 'observability.rules.custom_threshold' - ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId); - expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default'); - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.tags') - .contain('observability'); - expect(resp.hits.hits[0]._source).property( - 'kibana.alert.action_group', - 'custom_threshold.fired' - ); - expect(resp.hits.hits[0]._source).property('tags').contain('observability'); - expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*'); - expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open'); - expect(resp.hits.hits[0]._source).property('event.kind', 'signal'); - expect(resp.hits.hits[0]._source).property('event.action', 'open'); - - expect(resp.hits.hits[0]._source) - .property('kibana.alert.rule.parameters') - .eql({ - criteria: [ - { - comparator: '>', - threshold: [0.5], - timeSize: 5, - timeUnit: 'm', - metrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'p99' }], - }, - ], - alertOnNoData: true, - alertOnGroupDisappear: true, - searchConfiguration: { - index: MOCKED_AD_HOC_DATA_VIEW, - query: { query: '', language: 'kuery' }, - }, - }); - }); - - it('should set correct action variables', async () => { - const resp = await waitForDocumentInIndex({ - esClient, - indexName: ALERT_ACTION_INDEX, - retryService, - logger, - }); - - expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); - expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` - ); - expect(resp.hits.hits[0]._source?.reason).eql( - `99th percentile of system.cpu.user.pct is 250%, above the threshold of 50%. (duration: 5 mins, data view: ${DATA_VIEW_NAME})` - ); - expect(resp.hits.hits[0]._source?.value).eql('250%'); - - const parsedViewInAppUrl = parseSearchParams( - new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search - ); - - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); - expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_TITLE, - timeRange: { to: 'now' }, - query: { query: '', language: 'kuery' }, - filters: [], - }); - expect(parsedViewInAppUrl.params.timeRange.from).match(ISO_DATE_REGEX); - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/typings.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/typings.ts deleted file mode 100644 index 9002e9991292f..0000000000000 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/typings.ts +++ /dev/null @@ -1,25 +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 { Query, TimeRange } from '@kbn/es-query'; -import { SerializableRecord } from '@kbn/utility-types'; - -export interface ActionDocument { - ruleType: string; - alertDetailsUrl: string; - reason: string; - value: string; - viewInAppUrl: string; - host?: string; - group?: string; -} - -export interface LogsExplorerLocatorParsedParams extends SerializableRecord { - dataset: string; - timeRange: TimeRange; - query: Query; -} diff --git a/x-pack/test/alerting_api_integration/observability/index.ts b/x-pack/test/alerting_api_integration/observability/index.ts index 8b2f7b5b4c20e..8763332d504e0 100644 --- a/x-pack/test/alerting_api_integration/observability/index.ts +++ b/x-pack/test/alerting_api_integration/observability/index.ts @@ -10,13 +10,6 @@ export default function ({ loadTestFile }: any) { describe('Observability Rules', () => { describe('Rules Endpoints', () => { loadTestFile(require.resolve('./metric_threshold_rule')); - loadTestFile(require.resolve('./custom_threshold_rule/p99_pct_fired')); - loadTestFile(require.resolve('./custom_threshold_rule/rate_bytes_fired')); - loadTestFile(require.resolve('./custom_threshold_rule/avg_pct_no_data')); - loadTestFile(require.resolve('./custom_threshold_rule/avg_us_fired')); - loadTestFile(require.resolve('./custom_threshold_rule/custom_eq_avg_bytes_fired')); - loadTestFile(require.resolve('./custom_threshold_rule/documents_count_fired')); - loadTestFile(require.resolve('./custom_threshold_rule/group_by_fired')); loadTestFile(require.resolve('./custom_threshold_rule_data_view')); }); describe('Synthetics', () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts index e556db2e09a28..09823fccb5589 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule.ts @@ -33,6 +33,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { let dataForgeIndices: string[]; let actionId: string; let ruleId: string; + let dependencyRuleId: string; let adminRoleAuthc: RoleCredentials; let internalHeaders: InternalRequestHeader; @@ -76,6 +77,18 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { .delete(`/api/actions/connector/${actionId}`) .set(adminRoleAuthc.apiKeyHeader) .set(internalHeaders); + await esClient.deleteByQuery({ + index: RULE_ALERT_INDEX, + query: { + bool: { + should: [ + { term: { 'kibana.alert.rule.uuid': ruleId } }, + { term: { 'kibana.alert.rule.uuid': dependencyRuleId } }, + ], + }, + }, + conflicts: 'proceed', + }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', query: { term: { 'rule.id': ruleId } }, @@ -202,6 +215,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { actions: [], }); + dependencyRuleId = dependencyRule.id; const createdRule = await alertingApi.createRule({ roleAuthc: adminRoleAuthc, tags: ['observability'], diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts index 63530c98c26df..8ed42269e569b 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts @@ -27,11 +27,11 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const alertingApi = getService('alertingApi'); const dataViewApi = getService('dataViewApi'); const logger = getService('log'); - let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; const config = getService('config'); const isServerless = config.get('serverless'); const expectedConsumer = isServerless ? 'observability' : 'logs'; + let roleAuthc: RoleCredentials; + let internalReqHeader: InternalRequestHeader; describe('AVG - PCT - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts similarity index 86% rename from x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts index 4928e69a939c1..f439bb2fedca9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts @@ -5,31 +5,33 @@ * 2.0. */ +import { omit } from 'lodash'; +import expect from '@kbn/expect'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { NO_DATA_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; -import { omit } from 'lodash'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { kbnTestConfig } from '@kbn/test'; -import { FtrProviderContext } from '../../../ftr_provider_context'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { ISO_DATE_REGEX } from './constants'; -import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { ActionDocument, LogsExplorerLocatorParsedParams } from './types'; -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const esClient = getService('es'); - const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const alertingApi = getService('alertingApi'); const dataViewApi = getService('dataViewApi'); const esDeleteAllIndices = getService('esDeleteAllIndices'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); + const samlAuth = getService('samlAuth'); let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; - describe('Custom Threshold rule - AVG - PCT - NoData', () => { + describe('AVG - PCT - NoData', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_NAME = 'no-data-pattern-name'; @@ -40,18 +42,25 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); await dataViewApi.create({ name: DATA_VIEW_NAME, id: DATA_VIEW_ID, title: DATA_VIEW_TITLE, + roleAuthc, }); }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set(internalReqHeader); - await supertest.delete(`/api/actions/connector/${actionId}`).set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, @@ -64,9 +73,10 @@ export default function ({ getService }: FtrProviderContext) { }); await dataViewApi.delete({ id: DATA_VIEW_ID, + roleAuthc, }); await esDeleteAllIndices([ALERT_ACTION_INDEX]); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { @@ -80,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ roleAuthc, tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -144,7 +154,7 @@ export default function ({ getService }: FtrProviderContext) { it('should find the created rule with correct information about the consumer', async () => { const match = await alertingApi.findInRules(roleAuthc, ruleId); expect(match).not.to.be(undefined); - expect(match.consumer).to.be('observability'); + expect(match.consumer).to.be(expectedConsumer); }); it('should set correct information in the alert document', async () => { @@ -158,7 +168,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); @@ -208,7 +218,7 @@ export default function ({ getService }: FtrProviderContext) { docCountTarget: 1, }); - const { protocol, hostname, port } = kbnTestConfig.getUrlParts(); + const { protocol, hostname, port } = kbnTestConfig.getUrlPartsWithStrippedDefaultPort(); expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( `${protocol}://${hostname}${port ? `:${port}` : ''}/app/observability/alerts/${alertId}` diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_us_fired.ts similarity index 74% rename from x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_us_fired.ts index 391acf747eb25..05b6ded5191d1 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_us_fired.ts @@ -6,38 +6,37 @@ */ import moment from 'moment'; -import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { format } from 'url'; +import expect from '@kbn/expect'; +import { COMPARATORS } from '@kbn/alerting-comparators'; +import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { COMPARATORS } from '@kbn/alerting-comparators'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { getSyntraceClient, generateData } from '../helpers/syntrace'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { ActionDocument } from './typings'; +import { kbnTestConfig } from '@kbn/test'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { getSyntraceClient, generateData } from './helpers/syntrace'; +import { ActionDocument } from './types'; -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const start = moment(Date.now()).subtract(10, 'minutes').valueOf(); - const end = moment(Date.now()).valueOf(); + const end = moment(Date.now()).add(15, 'minutes').valueOf(); const esClient = getService('es'); + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esDeleteAllIndices = getService('esDeleteAllIndices'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); const config = getService('config'); const kibanaServerConfig = config.get('servers.kibana'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; const kibanaUrl = format(kibanaServerConfig); - const supertest = getService('supertest'); - const logger = getService('log'); - const retryService = getService('retry'); + let roleAuthc: RoleCredentials; + let internalReqHeader: InternalRequestHeader; - describe('Custom Threshold rule - AVG - US - FIRED', () => { + describe('AVG - US - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW = 'traces-apm*,metrics-apm*,logs-apm*'; @@ -50,20 +49,27 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); synthtraceEsClient = await getSyntraceClient({ esClient, kibanaUrl }); await generateData({ synthtraceEsClient, start, end }); - await createDataView({ - supertest, + await dataViewApi.create({ name: DATA_VIEW_NAME, id: DATA_VIEW_ID, title: DATA_VIEW, - logger, + roleAuthc, }); }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esDeleteAllIndices([ALERT_ACTION_INDEX]); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, @@ -71,37 +77,33 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, + query: { term: { 'kibana.alert.rule.consumer': expectedConsumer } }, }); await synthtraceEsClient.clean(); - await deleteDataView({ - supertest, + await dataViewApi.delete({ id: DATA_VIEW_ID, - logger, + roleAuthc, }); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, + actionId = await alertingApi.createIndexConnector({ + roleAuthc, name: 'Index Connector: Threshold API test', indexName: ALERT_ACTION_INDEX, - logger, }); - const createdRule = await createRule({ - supertest, - logger, - esClient, + const createdRule = await alertingApi.createRule({ + roleAuthc, tags: ['observability'], - consumer: 'logs', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { criteria: [ { - aggType: 'custom', comparator: COMPARATORS.GREATER_THAN, threshold: [7500000], timeSize: 5, @@ -148,23 +150,18 @@ export default function ({ getService }: FtrProviderContext) { }); it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, + const executionStatus = await alertingApi.waitForRuleStatus({ + roleAuthc, + ruleId, expectedStatus: 'active', - supertest, - retryService, - logger, }); - expect(executionStatus.status).to.be('active'); + expect(executionStatus).to.be('active'); }); it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, + const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, - retryService, - logger, }); alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; @@ -172,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); @@ -202,7 +199,6 @@ export default function ({ getService }: FtrProviderContext) { .eql({ criteria: [ { - aggType: 'custom', comparator: '>', threshold: [7500000], timeSize: 5, @@ -217,16 +213,14 @@ export default function ({ getService }: FtrProviderContext) { }); it('should set correct action parameter: ruleType', async () => { - const resp = await waitForDocumentInIndex({ - esClient, + const resp = await alertingApi.waitForDocumentInIndex({ indexName: ALERT_ACTION_INDEX, - retryService, - logger, + docCountTarget: 1, }); - + const { protocol, hostname, port } = kbnTestConfig.getUrlPartsWithStrippedDefaultPort(); expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` + `${protocol}://${hostname}${port ? `:${port}` : ''}/app/observability/alerts/${alertId}` ); expect(resp.hits.hits[0]._source?.reason).eql( `Average span.self_time.sum.us is 10,000,000, above the threshold of 7,500,000. (duration: 5 mins, data view: ${DATA_VIEW_NAME})` diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/custom_eq_avg_bytes_fired.ts similarity index 87% rename from x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/custom_eq_avg_bytes_fired.ts index f2f98fe1ff2de..4163c85c849f4 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/custom_eq_avg_bytes_fired.ts @@ -11,30 +11,32 @@ * 2.0. */ +import expect from '@kbn/expect'; import { cleanup, Dataset, generate, PartialConfig } from '@kbn/data-forge'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { kbnTestConfig } from '@kbn/test'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { ActionDocument } from './typings'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { ActionDocument } from './types'; -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const esClient = getService('es'); - const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esDeleteAllIndices = getService('esDeleteAllIndices'); const logger = getService('log'); const alertingApi = getService('alertingApi'); const dataViewApi = getService('dataViewApi'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); + const samlAuth = getService('samlAuth'); let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; - describe('Custom Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => { + describe('CUSTOM_EQ - AVG - BYTES - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; @@ -46,8 +48,8 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); dataForgeConfig = { schedule: [ { @@ -74,12 +76,19 @@ export default function ({ getService }: FtrProviderContext) { name: DATA_VIEW, id: DATA_VIEW_ID, title: DATA_VIEW, + roleAuthc, }); }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set(internalReqHeader); - await supertest.delete(`/api/actions/connector/${actionId}`).set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, @@ -92,10 +101,11 @@ export default function ({ getService }: FtrProviderContext) { }); await dataViewApi.delete({ id: DATA_VIEW_ID, + roleAuthc, }); await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); await cleanup({ client: esClient, config: dataForgeConfig, logger }); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { @@ -109,7 +119,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ roleAuthc, tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -174,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { it('should find the created rule with correct information about the consumer', async () => { const match = await alertingApi.findInRules(roleAuthc, ruleId); expect(match).not.to.be(undefined); - expect(match.consumer).to.be('observability'); + expect(match.consumer).to.be(expectedConsumer); }); it('should set correct information in the alert document', async () => { @@ -188,7 +198,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts similarity index 88% rename from x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts index ac0e034279cbe..f9d7067f0e0a4 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts @@ -5,33 +5,35 @@ * 2.0. */ +import { omit } from 'lodash'; +import expect from '@kbn/expect'; import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; -import { omit } from 'lodash'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { kbnTestConfig } from '@kbn/test'; -import { FtrProviderContext } from '../../../ftr_provider_context'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { ISO_DATE_REGEX } from './constants'; -import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { ActionDocument, LogsExplorerLocatorParsedParams } from './types'; -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const esClient = getService('es'); - const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esDeleteAllIndices = getService('esDeleteAllIndices'); const logger = getService('log'); const alertingApi = getService('alertingApi'); const dataViewApi = getService('dataViewApi'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); + const samlAuth = getService('samlAuth'); let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; - describe('Custom Threshold rule - DOCUMENTS_COUNT - FIRED', () => { + describe('DOCUMENTS_COUNT - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; @@ -44,8 +46,8 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); dataForgeConfig = { schedule: [ { @@ -75,12 +77,19 @@ export default function ({ getService }: FtrProviderContext) { name: DATA_VIEW_NAME, id: DATA_VIEW_ID, title: DATA_VIEW, + roleAuthc, }); }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set(internalReqHeader); - await supertest.delete(`/api/actions/connector/${actionId}`).set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, @@ -93,10 +102,11 @@ export default function ({ getService }: FtrProviderContext) { }); await dataViewApi.delete({ id: DATA_VIEW_ID, + roleAuthc, }); await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); await cleanup({ client: esClient, config: dataForgeConfig, logger }); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { @@ -110,7 +120,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ roleAuthc, tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -172,7 +182,7 @@ export default function ({ getService }: FtrProviderContext) { it('should find the created rule with correct information about the consumer', async () => { const match = await alertingApi.findInRules(roleAuthc, ruleId); expect(match).not.to.be(undefined); - expect(match.consumer).to.be('observability'); + expect(match.consumer).to.be(expectedConsumer); }); it('should set correct information in the alert document', async () => { @@ -186,7 +196,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts similarity index 89% rename from x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts index 3b38eed6b6166..3a554a16c5cec 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/group_by_fired.ts @@ -11,31 +11,33 @@ * 2.0. */ +import expect from '@kbn/expect'; import { kbnTestConfig } from '@kbn/test'; import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { COMPARATORS } from '@kbn/alerting-comparators'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { ActionDocument } from './typings'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { ActionDocument } from './types'; -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const esClient = getService('es'); - const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esDeleteAllIndices = getService('esDeleteAllIndices'); const logger = getService('log'); const alertingApi = getService('alertingApi'); const dataViewApi = getService('dataViewApi'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); + const samlAuth = getService('samlAuth'); let alertId: string; let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; - describe('Custom Threshold rule - GROUP_BY - FIRED', () => { + describe('GROUP_BY - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const DATA_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; @@ -46,8 +48,8 @@ export default function ({ getService }: FtrProviderContext) { let ruleId: string; before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); dataForgeConfig = { schedule: [ { @@ -74,12 +76,19 @@ export default function ({ getService }: FtrProviderContext) { name: DATA_VIEW, id: DATA_VIEW_ID, title: DATA_VIEW, + roleAuthc, }); }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set(internalReqHeader); - await supertest.delete(`/api/actions/connector/${actionId}`).set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, @@ -92,10 +101,11 @@ export default function ({ getService }: FtrProviderContext) { }); await dataViewApi.delete({ id: DATA_VIEW_ID, + roleAuthc, }); await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); await cleanup({ client: esClient, config: dataForgeConfig, logger }); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { @@ -109,7 +119,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ roleAuthc, tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -179,7 +189,7 @@ export default function ({ getService }: FtrProviderContext) { it('should find the created rule with correct information about the consumer', async () => { const match = await alertingApi.findInRules(roleAuthc, ruleId); expect(match).not.to.be(undefined); - expect(match.consumer).to.be('observability'); + expect(match.consumer).to.be(expectedConsumer); }); it('should set correct information in the alert document', async () => { @@ -193,7 +203,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/helpers/syntrace.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/helpers/syntrace.ts new file mode 100644 index 0000000000000..259924e80d64d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/helpers/syntrace.ts @@ -0,0 +1,159 @@ +/* + * 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 { Client } from '@elastic/elasticsearch'; +import { + ApmSynthtraceEsClient, + ApmSynthtraceKibanaClient, + createLogger, + LogLevel, +} from '@kbn/apm-synthtrace'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; + +export const getSyntraceClient = async ({ + kibanaUrl, + esClient, +}: { + kibanaUrl: string; + esClient: Client; +}) => { + const kibanaClient = new ApmSynthtraceKibanaClient({ + logger: createLogger(LogLevel.info), + target: kibanaUrl, + }); + const packageVersion = await kibanaClient.fetchLatestApmPackageVersion(); + return new ApmSynthtraceEsClient({ + client: esClient, + logger: createLogger(LogLevel.info), + refreshAfterIndex: true, + version: packageVersion, + }); +}; + +export const dataConfig = { + rate: 10, + transaction: { + name: 'GET /data', + duration: 1000, + }, + service: { + name: 'lambda-python-dev-hello', + version: '$LATEST', + runtime: { + name: 'AWS_Lambda_python3.8', + version: '3.8.11', + }, + framework: 'AWS Lambda', + agent: { + name: 'python', + version: '6.6.0', + }, + }, + containerOs: 'linux', + serverless: { + firstFunctionName: 'my-function-1', + secondFunctionName: 'my-function-2', + faasTriggerType: 'other', + }, + cloud: { + provider: 'aws', + availabilityZone: 'us-central1-c', + region: 'us-east-1', + machineType: 'e2-standard-4', + projectName: 'elastic-observability', + serviceName: 'lambda', + }, +}; + +export async function generateData({ + synthtraceEsClient, + start, + end, +}: { + synthtraceEsClient: ApmSynthtraceEsClient; + start: number; + end: number; +}) { + const { rate, service, containerOs, serverless, cloud, transaction } = dataConfig; + const { + provider, + availabilityZone, + region, + machineType, + projectName, + serviceName: cloudServiceName, + } = cloud; + const { faasTriggerType, firstFunctionName, secondFunctionName } = serverless; + const { version, runtime, framework, agent, name: serviceName } = service; + const { name: serviceRunTimeName, version: serviceRunTimeVersion } = runtime; + const { name: agentName, version: agentVersion } = agent; + + const instance = apm + .service({ name: serviceName, environment: 'production', agentName }) + .instance('instance-a'); + + const traceEvents = [ + timerange(start, end) + .interval('30s') + .rate(rate) + .generator((timestamp) => + instance + .containerId('instance-a') + .transaction({ transactionName: transaction.name }) + .timestamp(timestamp) + .defaults({ + 'cloud.provider': provider, + 'cloud.project.name': projectName, + 'cloud.service.name': cloudServiceName, + 'cloud.availability_zone': availabilityZone, + 'cloud.machine.type': machineType, + 'cloud.region': region, + 'faas.id': `arn:aws:lambda:us-west-2:123456789012:function:${firstFunctionName}`, + 'faas.trigger.type': faasTriggerType, + 'host.os.platform': containerOs, + 'kubernetes.pod.uid': '48f4c5a5-0625-4bea-9d94-77ee94a17e70', + 'service.version': version, + 'service.runtime.name': serviceRunTimeName, + 'service.runtime.version': serviceRunTimeVersion, + 'service.framework.name': framework, + 'agent.version': agentVersion, + }) + .duration(transaction.duration) + .success() + ), + + timerange(start, end) + .interval('30s') + .rate(rate) + .generator((timestamp) => + instance + .transaction({ transactionName: transaction.name }) + .timestamp(timestamp) + .defaults({ + 'cloud.provider': provider, + 'cloud.project.name': projectName, + 'cloud.service.name': cloudServiceName, + 'cloud.availability_zone': availabilityZone, + 'cloud.machine.type': machineType, + 'cloud.region': region, + 'faas.id': `arn:aws:lambda:us-west-2:123456789012:function:${secondFunctionName}`, + 'faas.trigger.type': faasTriggerType, + 'host.os.platform': containerOs, + 'kubernetes.pod.uid': '48f4c5a5-0625-4bea-9d94-77ee94a17e70', + 'service.version': version, + 'service.runtime.name': serviceRunTimeName, + 'service.runtime.version': serviceRunTimeVersion, + 'service.framework.name': framework, + 'agent.version': agentVersion, + }) + .duration(transaction.duration) + .success() + ), + ]; + + await synthtraceEsClient.index(traceEvents); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/index.ts index 505cbba20eb7c..96a3351043ae6 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/index.ts @@ -10,5 +10,12 @@ import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_c export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { describe('Custom Threshold rule', () => { loadTestFile(require.resolve('./avg_pct_fired')); + loadTestFile(require.resolve('./avg_pct_no_data')); + loadTestFile(require.resolve('./avg_us_fired')); + loadTestFile(require.resolve('./custom_eq_avg_bytes_fired')); + loadTestFile(require.resolve('./documents_count_fired')); + loadTestFile(require.resolve('./group_by_fired')); + loadTestFile(require.resolve('./p99_pct_fired')); + loadTestFile(require.resolve('./rate_bytes_fired')); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_pct_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts similarity index 88% rename from x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_pct_fired.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts index 291db4defac47..743bca2683895 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/p99_pct_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts @@ -5,32 +5,34 @@ * 2.0. */ +import { omit } from 'lodash'; import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { parseSearchParams } from '@kbn/share-plugin/common/url_service'; -import { omit } from 'lodash'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { kbnTestConfig } from '@kbn/test'; -import { FtrProviderContext } from '../../../ftr_provider_context'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; import { ISO_DATE_REGEX } from './constants'; -import { ActionDocument, LogsExplorerLocatorParsedParams } from './typings'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { ActionDocument, LogsExplorerLocatorParsedParams } from './types'; -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const esClient = getService('es'); - const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esDeleteAllIndices = getService('esDeleteAllIndices'); const alertingApi = getService('alertingApi'); const logger = getService('log'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); + const samlAuth = getService('samlAuth'); let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; - describe('Custom Threshold rule - P99 - PCT - FIRED', () => { + describe('P99 - PCT - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATA_VIEW_TITLE = 'kbn-data-forge-fake_hosts.fake_hosts-*'; @@ -54,8 +56,8 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); dataForgeConfig = { schedule: [ { @@ -81,8 +83,14 @@ export default function ({ getService }: FtrProviderContext) { }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set(internalReqHeader); - await supertest.delete(`/api/actions/connector/${actionId}`).set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, @@ -95,7 +103,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); await cleanup({ client: esClient, config: dataForgeConfig, logger }); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { @@ -109,7 +117,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ roleAuthc, tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -172,7 +180,7 @@ export default function ({ getService }: FtrProviderContext) { it('should find the created rule with correct information about the consumer', async () => { const match = await alertingApi.findInRules(roleAuthc, ruleId); expect(match).not.to.be(undefined); - expect(match.consumer).to.be('observability'); + expect(match.consumer).to.be(expectedConsumer); }); it('should set correct information in the alert document', async () => { @@ -186,7 +194,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'observability'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts similarity index 78% rename from x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts index d56310dd9b0d8..4011917659110 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/rate_bytes_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/rate_bytes_fired.ts @@ -5,31 +5,32 @@ * 2.0. */ +import expect from '@kbn/expect'; import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge'; +import type { InternalRequestHeader, RoleCredentials } from '@kbn/ftr-common-functional-services'; import { Aggregators } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/constants'; -import expect from '@kbn/expect'; import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; -import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; -import { createDataView, deleteDataView } from '../helpers/data_view'; -import { - waitForAlertInIndex, - waitForDocumentInIndex, - waitForRuleStatus, -} from '../helpers/alerting_wait_for_helpers'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { ActionDocument } from './typings'; +import { kbnTestConfig } from '@kbn/test'; +import { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; +import { ActionDocument } from './types'; -// eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const esClient = getService('es'); - const supertest = getService('supertest'); + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esDeleteAllIndices = getService('esDeleteAllIndices'); + const alertingApi = getService('alertingApi'); + const dataViewApi = getService('dataViewApi'); const logger = getService('log'); - const retryService = getService('retry'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'logs'; + let roleAuthc: RoleCredentials; + let internalReqHeader: InternalRequestHeader; - describe('Custom Threshold rule RATE - GROUP_BY - BYTES - FIRED', () => { + describe('RATE - GROUP_BY - BYTES - FIRED', () => { const CUSTOM_THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default'; const ALERT_ACTION_INDEX = 'alert-action-threshold'; const DATE_VIEW = 'kbn-data-forge-fake_hosts.fake_hosts-*'; @@ -41,6 +42,8 @@ export default function ({ getService }: FtrProviderContext) { let alertId: string; before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalReqHeader = samlAuth.getInternalRequestHeader(); dataForgeConfig = { schedule: [ { @@ -60,57 +63,56 @@ export default function ({ getService }: FtrProviderContext) { }, }; dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger }); - await waitForDocumentInIndex({ - esClient, + await alertingApi.waitForDocumentInIndex({ indexName: dataForgeIndices.join(','), docCountTarget: 270, - retryService, - logger, }); - await createDataView({ - supertest, + await dataViewApi.create({ name: DATE_VIEW, id: DATA_VIEW_ID, title: DATE_VIEW, - logger, + roleAuthc, }); }); after(async () => { - await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo'); - await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo'); + await supertestWithoutAuth + .delete(`/api/alerting/rule/${ruleId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); + await supertestWithoutAuth + .delete(`/api/actions/connector/${actionId}`) + .set(roleAuthc.apiKeyHeader) + .set(internalReqHeader); await esClient.deleteByQuery({ index: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, query: { term: { 'kibana.alert.rule.uuid': ruleId } }, }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, + query: { term: { 'kibana.alert.rule.consumer': expectedConsumer } }, }); - await deleteDataView({ - supertest, + await dataViewApi.delete({ id: DATA_VIEW_ID, - logger, + roleAuthc, }); await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]); await cleanup({ client: esClient, config: dataForgeConfig, logger }); + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('Rule creation', () => { it('creates rule successfully', async () => { - actionId = await createIndexConnector({ - supertest, + actionId = await alertingApi.createIndexConnector({ + roleAuthc, name: 'Index Connector: Threshold API test', indexName: ALERT_ACTION_INDEX, - logger, }); - const createdRule = await createRule({ - supertest, - logger, - esClient, + const createdRule = await alertingApi.createRule({ + roleAuthc, tags: ['observability'], - consumer: 'logs', + consumer: expectedConsumer, name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -165,23 +167,18 @@ export default function ({ getService }: FtrProviderContext) { }); it('should be active', async () => { - const executionStatus = await waitForRuleStatus({ - id: ruleId, + const executionStatus = await alertingApi.waitForRuleStatus({ + roleAuthc, + ruleId, expectedStatus: 'active', - supertest, - retryService, - logger, }); - expect(executionStatus.status).to.be('active'); + expect(executionStatus).to.be('active'); }); it('should set correct information in the alert document', async () => { - const resp = await waitForAlertInIndex({ - esClient, + const resp = await alertingApi.waitForAlertInIndex({ indexName: CUSTOM_THRESHOLD_RULE_ALERT_INDEX, ruleId, - retryService, - logger, }); alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid']; @@ -189,7 +186,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', expectedConsumer); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); @@ -256,16 +253,15 @@ export default function ({ getService }: FtrProviderContext) { }); it('should set correct action variables', async () => { - const resp = await waitForDocumentInIndex({ - esClient, + const resp = await alertingApi.waitForDocumentInIndex({ indexName: ALERT_ACTION_INDEX, - retryService, - logger, + docCountTarget: 1, }); + const { protocol, hostname, port } = kbnTestConfig.getUrlPartsWithStrippedDefaultPort(); expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold'); expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql( - `https://localhost:5601/app/observability/alerts/${alertId}` + `${protocol}://${hostname}${port ? `:${port}` : ''}/app/observability/alerts/${alertId}` ); expect(resp.hits.hits[0]._source?.reason).eql( `Rate of system.network.in.bytes is 60 kB/s, above or equal the threshold of 50 kB/s. (duration: 1 min, data view: kbn-data-forge-fake_hosts.fake_hosts-*, group: host-0,container-0)` diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/es_query_rule.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/es_query_rule.ts index 81527f552a928..1484158a9a991 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/es_query_rule.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/es_query_rule.ts @@ -25,6 +25,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { describe('ElasticSearch query rule', () => { const RULE_TYPE_ID = '.es-query'; const ALERT_ACTION_INDEX = 'alert-action-es-query'; + const RULE_ALERT_INDEX = '.alerts-stack.alerts-default'; let actionId: string; let ruleId: string; @@ -42,7 +43,11 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { .delete(`/api/actions/connector/${actionId}`) .set(adminRoleAuthc.apiKeyHeader) .set(internalReqHeader); - + await esClient.deleteByQuery({ + index: RULE_ALERT_INDEX, + query: { term: { 'kibana.alert.rule.uuid': ruleId } }, + conflicts: 'proceed', + }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', query: { term: { 'rule.id': ruleId } }, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/constants.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/constants.ts deleted file mode 100644 index 5cf1e0b4d6614..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/constants.ts +++ /dev/null @@ -1,8 +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. - */ - -export const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts deleted file mode 100644 index 01e91f1d5840b..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Custom Threshold Rule', function () { - loadTestFile(require.resolve('./avg_pct_no_data')); - loadTestFile(require.resolve('./documents_count_fired')); - loadTestFile(require.resolve('./custom_eq_avg_bytes_fired')); - loadTestFile(require.resolve('./group_by_fired')); - loadTestFile(require.resolve('./p99_pct_fired')); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/typings.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/typings.ts deleted file mode 100644 index 9002e9991292f..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/typings.ts +++ /dev/null @@ -1,25 +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 { Query, TimeRange } from '@kbn/es-query'; -import { SerializableRecord } from '@kbn/utility-types'; - -export interface ActionDocument { - ruleType: string; - alertDetailsUrl: string; - reason: string; - value: string; - viewInAppUrl: string; - host?: string; - group?: string; -} - -export interface LogsExplorerLocatorParsedParams extends SerializableRecord { - dataset: string; - timeRange: TimeRange; - query: Query; -} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts index 7ed6ca9517ac4..632335cd1c885 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/index.feature_flags.ts @@ -9,7 +9,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Serverless observability API - feature flags', function () { - loadTestFile(require.resolve('./custom_threshold_rule')); loadTestFile(require.resolve('./platform_security')); }); } diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index 1263efc17cf38..f963f295cd17d 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -32,8 +32,6 @@ "@kbn/telemetry-plugin", "@kbn/telemetry-collection-xpack-plugin", "@kbn/telemetry-tools", - "@kbn/observability-plugin", - "@kbn/data-forge", "@kbn/ftr-common-functional-services", "@kbn/apm-plugin", "@kbn/server-route-repository", @@ -80,12 +78,9 @@ "@kbn/reporting-common", "@kbn/es-query", "@kbn/slo-plugin", - "@kbn/share-plugin", "@kbn/es-query", - "@kbn/utility-types", "@kbn/synthetics-plugin", "@kbn/dataset-quality-plugin", - "@kbn/alerting-comparators", "@kbn/search-types", "@kbn/config-schema", "@kbn/features-plugin", From c35934e06291a765dec85d2f2a77a64d3973b97a Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Wed, 6 Nov 2024 13:06:45 +0100 Subject: [PATCH 04/53] [Search] [Playground] [Bug] Remove token clipping (#199055) - Remove token pruning functionality as this has a large cost, causing OOMs on serverless. - make the default model for openai gpt-4o - when the context is over the model limit, show a better error to the user for this - Update tests --------- Co-authored-by: Joseph McElroy --- .../search_playground/common/models.ts | 16 +- .../public/hooks/use_llms_models.test.ts | 22 +-- .../server/lib/conversational_chain.test.ts | 169 +++++++----------- .../server/lib/conversational_chain.ts | 38 ++-- .../search_playground/server/lib/errors.ts | 18 ++ .../search_playground/server/routes.test.ts | 25 +++ .../search_playground/server/routes.ts | 17 ++ .../page_objects/search_playground_page.ts | 2 +- 8 files changed, 161 insertions(+), 146 deletions(-) create mode 100644 x-pack/plugins/search_playground/server/lib/errors.ts diff --git a/x-pack/plugins/search_playground/common/models.ts b/x-pack/plugins/search_playground/common/models.ts index ca27c29e20533..85bf5ddfb0970 100644 --- a/x-pack/plugins/search_playground/common/models.ts +++ b/x-pack/plugins/search_playground/common/models.ts @@ -8,12 +8,6 @@ import { ModelProvider, LLMs } from './types'; export const MODELS: ModelProvider[] = [ - { - name: 'OpenAI GPT-3.5 Turbo', - model: 'gpt-3.5-turbo', - promptTokenLimit: 16385, - provider: LLMs.openai, - }, { name: 'OpenAI GPT-4o', model: 'gpt-4o', @@ -26,6 +20,12 @@ export const MODELS: ModelProvider[] = [ promptTokenLimit: 128000, provider: LLMs.openai, }, + { + name: 'OpenAI GPT-3.5 Turbo', + model: 'gpt-3.5-turbo', + promptTokenLimit: 16385, + provider: LLMs.openai, + }, { name: 'Anthropic Claude 3 Haiku', model: 'anthropic.claude-3-haiku-20240307-v1:0', @@ -40,13 +40,13 @@ export const MODELS: ModelProvider[] = [ }, { name: 'Google Gemini 1.5 Pro', - model: 'gemini-1.5-pro-001', + model: 'gemini-1.5-pro-002', promptTokenLimit: 2097152, provider: LLMs.gemini, }, { name: 'Google Gemini 1.5 Flash', - model: 'gemini-1.5-flash-001', + model: 'gemini-1.5-flash-002', promptTokenLimit: 2097152, provider: LLMs.gemini, }, diff --git a/x-pack/plugins/search_playground/public/hooks/use_llms_models.test.ts b/x-pack/plugins/search_playground/public/hooks/use_llms_models.test.ts index ebce3883a471b..c529a9d4b9aa6 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_llms_models.test.ts +++ b/x-pack/plugins/search_playground/public/hooks/use_llms_models.test.ts @@ -41,11 +41,11 @@ describe('useLLMsModels Hook', () => { connectorType: LLMs.openai, disabled: false, icon: expect.any(Function), - id: 'connectorId1OpenAI GPT-3.5 Turbo ', - name: 'OpenAI GPT-3.5 Turbo ', + id: 'connectorId1OpenAI GPT-4o ', + name: 'OpenAI GPT-4o ', showConnectorName: false, - value: 'gpt-3.5-turbo', - promptTokenLimit: 16385, + value: 'gpt-4o', + promptTokenLimit: 128000, }, { connectorId: 'connectorId1', @@ -53,10 +53,10 @@ describe('useLLMsModels Hook', () => { connectorType: LLMs.openai, disabled: false, icon: expect.any(Function), - id: 'connectorId1OpenAI GPT-4o ', - name: 'OpenAI GPT-4o ', + id: 'connectorId1OpenAI GPT-4 Turbo ', + name: 'OpenAI GPT-4 Turbo ', showConnectorName: false, - value: 'gpt-4o', + value: 'gpt-4-turbo', promptTokenLimit: 128000, }, { @@ -65,11 +65,11 @@ describe('useLLMsModels Hook', () => { connectorType: LLMs.openai, disabled: false, icon: expect.any(Function), - id: 'connectorId1OpenAI GPT-4 Turbo ', - name: 'OpenAI GPT-4 Turbo ', + id: 'connectorId1OpenAI GPT-3.5 Turbo ', + name: 'OpenAI GPT-3.5 Turbo ', showConnectorName: false, - value: 'gpt-4-turbo', - promptTokenLimit: 128000, + value: 'gpt-3.5-turbo', + promptTokenLimit: 16385, }, { connectorId: 'connectorId2', diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts index 13959e4455c29..5a56598e7387b 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts @@ -9,9 +9,8 @@ import type { Client } from '@elastic/elasticsearch'; import { BaseChatModel } from '@langchain/core/language_models/chat_models'; import { ChatPromptTemplate } from '@langchain/core/prompts'; import { FakeListChatModel, FakeStreamingLLM } from '@langchain/core/utils/testing'; -import { experimental_StreamData } from 'ai'; import { createAssist as Assist } from '../utils/assist'; -import { ConversationalChain, clipContext } from './conversational_chain'; +import { ConversationalChain, contextLimitCheck } from './conversational_chain'; import { ChatMessage, MessageRole } from '../types'; describe('conversational chain', () => { @@ -30,16 +29,20 @@ describe('conversational chain', () => { }: { responses: string[]; chat: ChatMessage[]; - expectedFinalAnswer: string; - expectedDocs: any; - expectedTokens: any; - expectedSearchRequest: any; + expectedFinalAnswer?: string; + expectedDocs?: any; + expectedTokens?: any; + expectedSearchRequest?: any; contentField?: Record; isChatModel?: boolean; docs?: any; expectedHasClipped?: boolean; modelLimit?: number; }) => { + if (expectedHasClipped) { + expect.assertions(1); + } + const searchMock = jest.fn().mockImplementation(() => { return { hits: { @@ -101,44 +104,52 @@ describe('conversational chain', () => { questionRewritePrompt: 'rewrite question {question} using {context}"', }); - const stream = await conversationalChain.stream(aiClient, chat); + try { + const stream = await conversationalChain.stream(aiClient, chat); - const streamToValue: string[] = await new Promise((resolve, reject) => { - const reader = stream.getReader(); - const textDecoder = new TextDecoder(); - const chunks: string[] = []; + const streamToValue: string[] = await new Promise((resolve, reject) => { + const reader = stream.getReader(); + const textDecoder = new TextDecoder(); + const chunks: string[] = []; - const read = () => { - reader.read().then(({ done, value }) => { - if (done) { - resolve(chunks); - } else { - chunks.push(textDecoder.decode(value)); - read(); - } - }, reject); - }; - read(); - }); + const read = () => { + reader.read().then(({ done, value }) => { + if (done) { + resolve(chunks); + } else { + chunks.push(textDecoder.decode(value)); + read(); + } + }, reject); + }; + read(); + }); - const textValue = streamToValue - .filter((v) => v[0] === '0') - .reduce((acc, v) => acc + v.replace(/0:"(.*)"\n/, '$1'), ''); - expect(textValue).toEqual(expectedFinalAnswer); + const textValue = streamToValue + .filter((v) => v[0] === '0') + .reduce((acc, v) => acc + v.replace(/0:"(.*)"\n/, '$1'), ''); + expect(textValue).toEqual(expectedFinalAnswer); - const annotations = streamToValue - .filter((v) => v[0] === '8') - .map((entry) => entry.replace(/8:(.*)\n/, '$1'), '') - .map((entry) => JSON.parse(entry)) - .reduce((acc, v) => acc.concat(v), []); + const annotations = streamToValue + .filter((v) => v[0] === '8') + .map((entry) => entry.replace(/8:(.*)\n/, '$1'), '') + .map((entry) => JSON.parse(entry)) + .reduce((acc, v) => acc.concat(v), []); - const docValues = annotations.filter((v: { type: string }) => v.type === 'retrieved_docs'); - const tokens = annotations.filter((v: { type: string }) => v.type.endsWith('_token_count')); - const hasClipped = !!annotations.some((v: { type: string }) => v.type === 'context_clipped'); - expect(docValues).toEqual(expectedDocs); - expect(tokens).toEqual(expectedTokens); - expect(hasClipped).toEqual(expectedHasClipped); - expect(searchMock.mock.calls[0]).toEqual(expectedSearchRequest); + const docValues = annotations.filter((v: { type: string }) => v.type === 'retrieved_docs'); + const tokens = annotations.filter((v: { type: string }) => v.type.endsWith('_token_count')); + const hasClipped = !!annotations.some((v: { type: string }) => v.type === 'context_clipped'); + expect(docValues).toEqual(expectedDocs); + expect(tokens).toEqual(expectedTokens); + expect(hasClipped).toEqual(expectedHasClipped); + expect(searchMock.mock.calls[0]).toEqual(expectedSearchRequest); + } catch (error) { + if (expectedHasClipped) { + expect(error).toMatchInlineSnapshot(`[ContextLimitError: Context exceeds the model limit]`); + } else { + throw error; + } + } }; it('should be able to create a conversational chain', async () => { @@ -470,102 +481,56 @@ describe('conversational chain', () => { }, ], modelLimit: 100, - expectedFinalAnswer: 'the final answer', - expectedDocs: [ - { - documents: [ - { - metadata: { _id: '1', _index: 'index' }, - pageContent: expect.any(String), - }, - { - metadata: { _id: '1', _index: 'website' }, - pageContent: expect.any(String), - }, - ], - type: 'retrieved_docs', - }, - ], - // Even with body_content of 1000, the token count should be below or equal to model limit of 100 - expectedTokens: [ - { type: 'context_token_count', count: 63 }, - { type: 'prompt_token_count', count: 97 }, - ], expectedHasClipped: true, - expectedSearchRequest: [ - { - method: 'POST', - path: '/index,website/_search', - body: { query: { match: { field: 'rewrite "the" question' } }, size: 3 }, - }, - ], isChatModel: false, }); }, 10000); - describe('clipContext', () => { + describe('contextLimitCheck', () => { const prompt = ChatPromptTemplate.fromTemplate( 'you are a QA bot {question} {chat_history} {context}' ); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should return the input as is if modelLimit is undefined', async () => { const input = { context: 'This is a test context.', question: 'This is a test question.', chat_history: 'This is a test chat history.', }; + jest.spyOn(prompt, 'format'); + const result = await contextLimitCheck(undefined, prompt)(input); - const data = new experimental_StreamData(); - const appendMessageAnnotationSpy = jest.spyOn(data, 'appendMessageAnnotation'); - - const result = await clipContext(undefined, prompt, data)(input); - expect(result).toEqual(input); - expect(appendMessageAnnotationSpy).not.toHaveBeenCalled(); + expect(result).toBe(input); + expect(prompt.format).not.toHaveBeenCalled(); }); - it('should not clip context if within modelLimit', async () => { + it('should return the input if within modelLimit', async () => { const input = { context: 'This is a test context.', question: 'This is a test question.', chat_history: 'This is a test chat history.', }; - const data = new experimental_StreamData(); - const appendMessageAnnotationSpy = jest.spyOn(data, 'appendMessageAnnotation'); - const result = await clipContext(10000, prompt, data)(input); + jest.spyOn(prompt, 'format'); + const result = await contextLimitCheck(10000, prompt)(input); expect(result).toEqual(input); - expect(appendMessageAnnotationSpy).not.toHaveBeenCalled(); + expect(prompt.format).toHaveBeenCalledWith(input); }); it('should clip context if exceeds modelLimit', async () => { + expect.assertions(1); const input = { context: 'This is a test context.\nThis is another line.\nAnd another one.', question: 'This is a test question.', chat_history: 'This is a test chat history.', }; - const data = new experimental_StreamData(); - const appendMessageAnnotationSpy = jest.spyOn(data, 'appendMessageAnnotation'); - const result = await clipContext(33, prompt, data)(input); - expect(result.context).toBe('This is a test context.\nThis is another line.'); - expect(appendMessageAnnotationSpy).toHaveBeenCalledWith({ - type: 'context_clipped', - count: 4, - }); - }); - it('exit when context becomes empty', async () => { - const input = { - context: 'This is a test context.\nThis is another line.\nAnd another one.', - question: 'This is a test question.', - chat_history: 'This is a test chat history.', - }; - const data = new experimental_StreamData(); - const appendMessageAnnotationSpy = jest.spyOn(data, 'appendMessageAnnotation'); - const result = await clipContext(1, prompt, data)(input); - expect(result.context).toBe(''); - expect(appendMessageAnnotationSpy).toHaveBeenCalledWith({ - type: 'context_clipped', - count: 15, - }); + await expect(contextLimitCheck(33, prompt)(input)).rejects.toMatchInlineSnapshot( + `[ContextLimitError: Context exceeds the model limit]` + ); }); }); }); diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts index 922f672bda5c6..dcd1f4189bc75 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts @@ -25,6 +25,7 @@ import { renderTemplate } from '../utils/render_template'; import { AssistClient } from '../utils/assist'; import { getCitations } from '../utils/get_citations'; import { getTokenEstimate, getTokenEstimateFromMessages } from './token_tracking'; +import { ContextLimitError } from './errors'; interface RAGOptions { index: string; @@ -88,37 +89,26 @@ position: ${i + 1} return serializedDocs.join('\n'); }; -export function clipContext( +export function contextLimitCheck( modelLimit: number | undefined, - prompt: ChatPromptTemplate, - data: experimental_StreamData + prompt: ChatPromptTemplate ): (input: ContextInputs) => Promise { return async (input) => { if (!modelLimit) return input; - let context = input.context; - const clippedContext = []; - while ( - getTokenEstimate(await prompt.format({ ...input, context })) > modelLimit && - context.length > 0 - ) { - // remove the last paragraph - const lines = context.split('\n'); - clippedContext.push(lines.pop()); - context = lines.join('\n'); - } + const stringPrompt = await prompt.format(input); + const approxPromptTokens = getTokenEstimate(stringPrompt); + const aboveContextLimit = approxPromptTokens > modelLimit; - if (clippedContext.length > 0) { - data.appendMessageAnnotation({ - type: 'context_clipped', - count: getTokenEstimate(clippedContext.join('\n')), - }); + if (aboveContextLimit) { + throw new ContextLimitError( + 'Context exceeds the model limit', + modelLimit, + approxPromptTokens + ); } - return { - ...input, - context, - }; + return input; }; } @@ -205,7 +195,7 @@ class ConversationalChainFn { }); return inputs; }), - RunnableLambda.from(clipContext(this.options?.rag?.inputTokensLimit, prompt, data)), + RunnableLambda.from(contextLimitCheck(this.options?.rag?.inputTokensLimit, prompt)), RunnableLambda.from(registerContextTokenCounts(data)), prompt, this.options.model.withConfig({ metadata: { type: 'question_answer_qa' } }), diff --git a/x-pack/plugins/search_playground/server/lib/errors.ts b/x-pack/plugins/search_playground/server/lib/errors.ts new file mode 100644 index 0000000000000..38441b607a64a --- /dev/null +++ b/x-pack/plugins/search_playground/server/lib/errors.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export class ContextLimitError extends Error { + public modelLimit: number; + public currentTokens: number; + + constructor(message: string, modelLimit: number, currentTokens: number) { + super(message); + this.name = 'ContextLimitError'; + this.modelLimit = modelLimit; + this.currentTokens = currentTokens; + } +} diff --git a/x-pack/plugins/search_playground/server/routes.test.ts b/x-pack/plugins/search_playground/server/routes.test.ts index 018b1420a46cf..fca1adab5862b 100644 --- a/x-pack/plugins/search_playground/server/routes.test.ts +++ b/x-pack/plugins/search_playground/server/routes.test.ts @@ -12,6 +12,7 @@ import { MockRouter } from '../__mocks__/router.mock'; import { ConversationalChain } from './lib/conversational_chain'; import { getChatParams } from './lib/get_chat_params'; import { createRetriever, defineRoutes } from './routes'; +import { ContextLimitError } from './lib/errors'; jest.mock('./lib/get_chat_params', () => ({ getChatParams: jest.fn(), @@ -100,5 +101,29 @@ describe('Search Playground routes', () => { }, }); }); + + it('responds with context error message if there is ContextLimitError', async () => { + (getChatParams as jest.Mock).mockResolvedValue({ model: 'open-ai' }); + (ConversationalChain as jest.Mock).mockImplementation(() => { + return { + stream: jest + .fn() + .mockRejectedValue( + new ContextLimitError('Context exceeds the model limit', 16385, 24000) + ), + }; + }); + + await mockRouter.callRoute({ + body: mockRequestBody, + }); + + expect(mockRouter.response.badRequest).toHaveBeenCalledWith({ + body: { + message: + 'Your request uses 24000 input tokens. This exceeds the model token limit of 16385 tokens. Please try using a different model thats capable of accepting larger prompts or reducing the prompt by decreasing the size of the context documents. If you are unsure, please see our documentation.', + }, + }); + }); }); }); diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts index c26a342aace49..3cdebe11c02c2 100644 --- a/x-pack/plugins/search_playground/server/routes.ts +++ b/x-pack/plugins/search_playground/server/routes.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import type { Logger } from '@kbn/logging'; import { IRouter, StartServicesAccessor } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; import { sendMessageEvent, SendMessageEventData } from './analytics/events'; import { fetchFields } from './lib/fetch_query_source_fields'; import { AssistClientOptionsWithClient, createAssist as Assist } from './utils/assist'; @@ -23,6 +24,7 @@ import { getChatParams } from './lib/get_chat_params'; import { fetchIndices } from './lib/fetch_indices'; import { isNotNullish } from '../common/is_not_nullish'; import { MODELS } from '../common/models'; +import { ContextLimitError } from './lib/errors'; export function createRetriever(esQuery: string) { return (question: string) => { @@ -157,6 +159,21 @@ export function defineRoutes({ isCloud: cloud?.isCloudEnabled ?? false, }); } catch (e) { + if (e instanceof ContextLimitError) { + return response.badRequest({ + body: { + message: i18n.translate( + 'xpack.searchPlayground.serverErrors.exceedsModelTokenLimit', + { + defaultMessage: + 'Your request uses {approxPromptTokens} input tokens. This exceeds the model token limit of {modelLimit} tokens. Please try using a different model thats capable of accepting larger prompts or reducing the prompt by decreasing the size of the context documents. If you are unsure, please see our documentation.', + values: { modelLimit: e.modelLimit, approxPromptTokens: e.currentTokens }, + } + ), + }, + }); + } + logger.error('Failed to create the chat stream', e); if (typeof e === 'object') { diff --git a/x-pack/test/functional/page_objects/search_playground_page.ts b/x-pack/test/functional/page_objects/search_playground_page.ts index 9b44addce9e25..3a47da067097f 100644 --- a/x-pack/test/functional/page_objects/search_playground_page.ts +++ b/x-pack/test/functional/page_objects/search_playground_page.ts @@ -146,7 +146,7 @@ export function SearchPlaygroundPageProvider({ getService }: FtrProviderContext) const model = await testSubjects.find('summarizationModelSelect'); const defaultModel = await model.getVisibleText(); - expect(defaultModel).to.equal('OpenAI GPT-3.5 Turbo'); + expect(defaultModel).to.equal('OpenAI GPT-4o'); expect(defaultModel).not.to.be.empty(); expect( From 7608d76ac3b7688bea295b206a0ad075a2c69fb8 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 6 Nov 2024 13:09:07 +0100 Subject: [PATCH 05/53] [kbn-test] add forceNewSession option to re-generate session cookie (#199018) ## Summary Currently FTR caches `Cookies` of Cloud SAML sessions for the complete FTR config run, meaning we only perform actual login once for the specified role. It helps to optimise tests run time and improve stability. While it works most of the time, according to https://github.com/elastic/kibana/issues/71866 Reporting test suite stability depends on token validity (`20m`) and to stabilize it, this PR adds `forceNewSession` option to force request a new SAML session when it is required for specific tests. ``` cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin', { forceNewSession: true, }); ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../services/saml_auth/saml_auth_provider.ts | 35 +++++++++-- packages/kbn-test/index.ts | 7 ++- packages/kbn-test/src/auth/index.ts | 3 +- .../kbn-test/src/auth/session_manager.test.ts | 59 ++++++++++++++++++ packages/kbn-test/src/auth/session_manager.ts | 61 +++++++++++++------ packages/kbn-test/src/auth/types.ts | 5 ++ .../common/reporting/datastream.ts | 6 +- .../common/reporting/generate_csv_discover.ts | 4 +- 8 files changed, 149 insertions(+), 31 deletions(-) diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts index efc86f85213c0..c317645cc921b 100644 --- a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SamlSessionManager } from '@kbn/test'; +import { GetCookieOptions, SamlSessionManager } from '@kbn/test'; import expect from '@kbn/expect'; import { REPO_ROOT } from '@kbn/repo-info'; import { resolve } from 'path'; @@ -91,16 +91,39 @@ export function SamlAuthProvider({ getService }: FtrProviderContext) { }; return { - async getInteractiveUserSessionCookieWithRoleScope(role: string) { + /** + * Returns a Cookie string containing the session token for the specified role. + * This string can be used to update browser cookies and login with the designated role. + * + * @param role - The SAML role for which the session token is required. + * @param options - Optional settings to control session behavior, such as forcing a new session. + * @returns A string with the Cookie token + * + * @throws If the specified role is a custom role without a predefined descriptor. + */ + async getInteractiveUserSessionCookieWithRoleScope(role: string, options?: GetCookieOptions) { // Custom role has no descriptors by default, check if it was added before authentication throwIfRoleNotSet(role, CUSTOM_ROLE, supportedRoleDescriptors); - return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); + return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role, options); }, - async getM2MApiCookieCredentialsWithRoleScope(role: string): Promise { + /** + * Returns an object containing a Cookie header with the session token for the specified role. + * This header can be used for authenticating API requests as the designated role. + * + * @param role - The SAML role for which the session token is required. + * @param options - Optional settings to control session behavior, such as forcing a new session. + * @returns An object with the Cookie header for API authentication. + * + * @throws If the specified role is a custom role without a predefined descriptor. + */ + async getM2MApiCookieCredentialsWithRoleScope( + role: string, + options?: GetCookieOptions + ): Promise { // Custom role has no descriptors by default, check if it was added before authentication throwIfRoleNotSet(role, CUSTOM_ROLE, supportedRoleDescriptors); - return sessionManager.getApiCredentialsForRole(role); + return sessionManager.getApiCredentialsForRole(role, options); }, async getEmail(role: string) { @@ -195,7 +218,7 @@ export function SamlAuthProvider({ getService }: FtrProviderContext) { expect(status).to.be(204); - // Update descriptors for custome role, it will be used to create API key + // Update descriptors for the custom role, it will be used to create API key supportedRoleDescriptors.set(CUSTOM_ROLE, customRoleDescriptors); }, diff --git a/packages/kbn-test/index.ts b/packages/kbn-test/index.ts index 3c03a32efa9ea..57d9c767827df 100644 --- a/packages/kbn-test/index.ts +++ b/packages/kbn-test/index.ts @@ -14,7 +14,12 @@ export { startServersCli, startServers } from './src/functional_tests/start_serv // @internal export { runTestsCli, runTests } from './src/functional_tests/run_tests'; -export { SamlSessionManager, type SamlSessionManagerOptions, type HostOptions } from './src/auth'; +export { + SamlSessionManager, + type SamlSessionManagerOptions, + type HostOptions, + type GetCookieOptions, +} from './src/auth'; export { runElasticsearch, runKibanaServer } from './src/functional_tests/lib'; export { getKibanaCliArg, getKibanaCliLoggers } from './src/functional_tests/lib/kibana_cli_args'; diff --git a/packages/kbn-test/src/auth/index.ts b/packages/kbn-test/src/auth/index.ts index 61dd5853873b3..b16ddc6951944 100644 --- a/packages/kbn-test/src/auth/index.ts +++ b/packages/kbn-test/src/auth/index.ts @@ -9,6 +9,7 @@ export { SamlSessionManager, - type SamlSessionManagerOptions, + type GetCookieOptions, type HostOptions, + type SamlSessionManagerOptions, } from './session_manager'; diff --git a/packages/kbn-test/src/auth/session_manager.test.ts b/packages/kbn-test/src/auth/session_manager.test.ts index 4b20581eced4c..284432574833f 100644 --- a/packages/kbn-test/src/auth/session_manager.test.ts +++ b/packages/kbn-test/src/auth/session_manager.test.ts @@ -8,6 +8,7 @@ */ import { ToolingLog } from '@kbn/tooling-log'; +import crypto from 'crypto'; import { Cookie } from 'tough-cookie'; import { Session } from './saml_auth'; import { SamlSessionManager, SupportedRoles } from './session_manager'; @@ -33,6 +34,8 @@ const getSecurityProfileMock = jest.spyOn(samlAuth, 'getSecurityProfile'); const readCloudUsersFromFileMock = jest.spyOn(helper, 'readCloudUsersFromFile'); const isValidHostnameMock = jest.spyOn(helper, 'isValidHostname'); +const getTestToken = () => 'kbn_cookie_' + crypto.randomBytes(16).toString('hex'); + jest.mock('../kbn_client/kbn_client', () => { return { KbnClient: jest.fn(), @@ -105,6 +108,34 @@ describe('SamlSessionManager', () => { expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); }); + test(`'getSessionCookieForRole' should call 'createLocalSAMLSession' again if 'forceNewSession = true'`, async () => { + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + createLocalSAMLSessionMock.mockResolvedValueOnce( + new Session( + Cookie.parse(`sid=${getTestToken()}; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT`)!, + testEmail + ) + ); + const cookieStr1 = await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope( + roleViewer + ); + createLocalSAMLSessionMock.mockResolvedValueOnce( + new Session( + Cookie.parse(`sid=${getTestToken()}; Path=/; Expires=Wed, 01 Oct 2023 08:00:00 GMT`)!, + testEmail + ) + ); + const cookieStr2 = await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope( + roleViewer, + { + forceNewSession: true, + } + ); + expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(2); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); + expect(cookieStr1).not.toEqual(cookieStr2); + }); + test(`'getEmail' return the correct email`, async () => { const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); const email = await samlSessionManager.getEmail(roleEditor); @@ -255,6 +286,34 @@ describe('SamlSessionManager', () => { expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(2); }); + test(`'getSessionCookieForRole' should call 'createCloudSAMLSession' again if 'forceNewSession = true'`, async () => { + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + createCloudSAMLSessionMock.mockResolvedValueOnce( + new Session( + Cookie.parse(`sid=${getTestToken()}; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT`)!, + cloudEmail + ) + ); + const cookieStr1 = await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope( + roleViewer + ); + createCloudSAMLSessionMock.mockResolvedValueOnce( + new Session( + Cookie.parse(`sid=${getTestToken()}; Path=/; Expires=Wed, 01 Oct 2023 08:00:00 GMT`)!, + cloudEmail + ) + ); + const cookieStr2 = await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope( + roleViewer, + { + forceNewSession: true, + } + ); + expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(0); + expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(2); + expect(cookieStr1).not.toEqual(cookieStr2); + }); + test(`'getEmail' return the correct email`, async () => { const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); const email = await samlSessionManager.getEmail(roleViewer); diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index ba411aaa21891..4efd55a71aad5 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -17,7 +17,7 @@ import { getSecurityProfile, Session, } from './saml_auth'; -import { Role, User } from './types'; +import { GetSessionByRole, Role, User } from './types'; export interface HostOptions { protocol: 'http' | 'https'; @@ -40,6 +40,10 @@ export interface SupportedRoles { roles: string[]; } +export interface GetCookieOptions { + forceNewSession: boolean; +} + /** * Manages cookies associated with user roles */ @@ -115,24 +119,32 @@ Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment` } }; - private getSessionByRole = async (role: string) => { - if (this.sessionCache.has(role)) { + private getSessionByRole = async (options: GetSessionByRole): Promise => { + const { role, forceNewSession } = options; + + // Validate role before creating SAML session + this.validateRole(role); + + // Check if session is cached and not forced to create the new one + if (!forceNewSession && this.sessionCache.has(role)) { return this.sessionCache.get(role)!; } - // Validate role before creating SAML session - if (this.supportedRoles && !this.supportedRoles.roles.includes(role)) { - throw new Error( - `Role '${role}' is not in the supported list: ${this.supportedRoles.roles.join( - ', ' - )}. Add role descriptor in ${this.supportedRoles.sourcePath} to enable it for testing` - ); + const session = await this.createSessionForRole(role); + this.sessionCache.set(role, session); + + if (forceNewSession) { + this.log.debug(`Session for role '${role}' was force updated.`); } + return session; + }; + + private createSessionForRole = async (role: string): Promise => { let session: Session; if (this.isCloud) { - this.log.debug(`new cloud SAML authentication with '${role}' role`); + this.log.debug(`Creating new cloud SAML session for role '${role}'`); const kbnVersion = await this.kbnClient.version.get(); const { email, password } = this.getCloudUserByRole(role); session = await createCloudSAMLSession({ @@ -143,7 +155,7 @@ Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment` log: this.log, }); } else { - this.log.debug(`new fake SAML authentication with '${role}' role`); + this.log.debug(`Creating new local SAML session for role '${role}'`); session = await createLocalSAMLSession({ username: `elastic_${role}`, email: `elastic_${role}@elastic.co`, @@ -154,27 +166,38 @@ Set env variable 'TEST_CLOUD=1' to run FTR against your Cloud deployment` }); } - this.sessionCache.set(role, session); return session; }; - async getApiCredentialsForRole(role: string) { - const session = await this.getSessionByRole(role); + private validateRole = (role: string): void => { + if (this.supportedRoles && !this.supportedRoles.roles.includes(role)) { + throw new Error( + `Role '${role}' is not in the supported list: ${this.supportedRoles.roles.join( + ', ' + )}. Add role descriptor in ${this.supportedRoles.sourcePath} to enable it for testing` + ); + } + }; + + async getApiCredentialsForRole(role: string, options?: GetCookieOptions) { + const { forceNewSession } = options || { forceNewSession: false }; + const session = await this.getSessionByRole({ role, forceNewSession }); return { Cookie: `sid=${session.getCookieValue()}` }; } - async getInteractiveUserSessionCookieWithRoleScope(role: string) { - const session = await this.getSessionByRole(role); + async getInteractiveUserSessionCookieWithRoleScope(role: string, options?: GetCookieOptions) { + const { forceNewSession } = options || { forceNewSession: false }; + const session = await this.getSessionByRole({ role, forceNewSession }); return session.getCookieValue(); } async getEmail(role: string) { - const session = await this.getSessionByRole(role); + const session = await this.getSessionByRole({ role, forceNewSession: false }); return session.email; } async getUserData(role: string) { - const { cookie } = await this.getSessionByRole(role); + const { cookie } = await this.getSessionByRole({ role, forceNewSession: false }); const profileData = await getSecurityProfile({ kbnHost: this.kbnHost, cookie, log: this.log }); return profileData; } diff --git a/packages/kbn-test/src/auth/types.ts b/packages/kbn-test/src/auth/types.ts index 170793b8950a1..4a61f71d5d572 100644 --- a/packages/kbn-test/src/auth/types.ts +++ b/packages/kbn-test/src/auth/types.ts @@ -61,3 +61,8 @@ export interface RetryParams { attemptsCount: number; attemptDelay: number; } + +export interface GetSessionByRole { + role: string; + forceNewSession: boolean; +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts index a4eb181e6bc2b..325f053134a67 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/datastream.ts @@ -33,12 +33,12 @@ export default function ({ getService }: FtrProviderContext) { }; describe('Data Stream', function () { - // see details: https://github.com/elastic/kibana/issues/198811 - this.tags(['failsOnMKI']); const generatedReports = new Set(); before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin', { + forceNewSession: true, + }); internalReqHeader = svlCommonApi.getInternalRequestHeader(); await esArchiver.load(archives.ecommerce.data); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts b/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts index c654e5e307f86..3ab3037bdb359 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/reporting/generate_csv_discover.ts @@ -79,7 +79,9 @@ export default function ({ getService }: FtrProviderContext) { this.timeout(12 * 60 * 1000); before(async () => { - cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin'); + cookieCredentials = await samlAuth.getM2MApiCookieCredentialsWithRoleScope('admin', { + forceNewSession: true, + }); internalReqHeader = svlCommonApi.getInternalRequestHeader(); }); From 0e99a779e29946377123071dfcf61e14c21300d9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:09:41 +1100 Subject: [PATCH 06/53] Authorized route migration for routes owned by security-entity-analytics (#198385) ### Authz API migration for authorized routes This PR migrates `access:` tags used in route definitions to 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:** Access control tags were defined in the `options` object of the route: ```ts router.get({ path: '/api/path', options: { tags: ['access:', 'access:'], }, ... }, handler); ``` ### **After migration:** Tags have been replaced with the more robust `security.authz.requiredPrivileges` field under `security`: ```ts router.get({ path: '/api/path', security: { authz: { requiredPrivileges: ['', ''], }, }, ... }, handler); ``` ### What to do next? 1. Review the changes in this PR. 2. You might need to update your tests to reflect the new security configuration: - If you have tests that rely on checking `access` tags. - If you have snapshot tests that include the route definition. - If you have FTR tests that rely on checking unauthorized error message. The error message changed to also include missing privileges. ## 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: Pablo Machado --- .../asset_criticality/routes/bulk_upload.ts | 6 ++++-- .../asset_criticality/routes/delete.ts | 6 ++++-- .../entity_analytics/asset_criticality/routes/get.ts | 6 ++++-- .../asset_criticality/routes/list.ts | 6 ++++-- .../asset_criticality/routes/privileges.ts | 6 ++++-- .../asset_criticality/routes/status.ts | 6 ++++-- .../asset_criticality/routes/upload_csv.ts | 6 +++++- .../asset_criticality/routes/upsert.ts | 6 ++++-- .../entity_store/routes/apply_dataview_indices.ts | 6 ++++-- .../entity_analytics/entity_store/routes/delete.ts | 6 ++++-- .../entity_store/routes/entities/list.ts | 6 ++++-- .../lib/entity_analytics/entity_store/routes/get.ts | 6 ++++-- .../lib/entity_analytics/entity_store/routes/init.ts | 6 ++++-- .../lib/entity_analytics/entity_store/routes/list.ts | 6 ++++-- .../entity_analytics/entity_store/routes/start.ts | 6 ++++-- .../entity_analytics/entity_store/routes/stats.ts | 6 ++++-- .../lib/entity_analytics/entity_store/routes/stop.ts | 6 ++++-- .../entity_analytics/risk_engine/routes/delete.ts | 6 ++++-- .../entity_analytics/risk_engine/routes/disable.ts | 6 ++++-- .../entity_analytics/risk_engine/routes/enable.ts | 6 ++++-- .../lib/entity_analytics/risk_engine/routes/init.ts | 6 ++++-- .../risk_engine/routes/privileges.ts | 6 ++++-- .../risk_engine/routes/schedule_now.ts | 6 ++++-- .../entity_analytics/risk_engine/routes/settings.ts | 6 ++++-- .../entity_analytics/risk_engine/routes/status.ts | 6 ++++-- .../risk_score/routes/entity_calculation.ts | 12 ++++++++---- .../entity_analytics/risk_score/routes/preview.ts | 6 ++++-- .../server/lib/risk_score/index_status/index.ts | 6 ++++-- .../lib/risk_score/indices/create_index_route.ts | 6 ++++-- .../lib/risk_score/indices/delete_indices_route.ts | 6 ++++-- .../onboarding/routes/install_risk_scores.ts | 6 ++++-- .../routes/read_prebuilt_dev_tool_content_route.ts | 6 ++++-- .../routes/create_prebuilt_saved_objects.ts | 6 ++++-- .../routes/delete_prebuilt_saved_objects.ts | 6 ++++-- .../risk_score/stored_scripts/create_script_route.ts | 6 ++++-- .../risk_score/stored_scripts/delete_script_route.ts | 6 ++++-- 36 files changed, 149 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts index 93251bcf92652..e0de56da88c8d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts @@ -33,8 +33,10 @@ export const assetCriticalityPublicBulkUploadRoute = ( .post({ access: 'public', path: ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts index 6c2437081500d..de3f8cda4f5ba 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts @@ -28,8 +28,10 @@ export const assetCriticalityPublicDeleteRoute = ( .delete({ access: 'public', path: ASSET_CRITICALITY_PUBLIC_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts index 048df61757a56..2cea50a2bbe20 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts @@ -30,8 +30,10 @@ export const assetCriticalityPublicGetRoute = ( .get({ access: 'public', path: ASSET_CRITICALITY_PUBLIC_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts index a6316646bc612..7cfc763e8b97c 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts @@ -28,8 +28,10 @@ export const assetCriticalityPublicListRoute = ( .get({ access: 'public', path: ASSET_CRITICALITY_PUBLIC_LIST_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts index 8c40335423973..59cdc983770ca 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts @@ -28,8 +28,10 @@ export const assetCriticalityInternalPrivilegesRoute = ( .get({ access: 'internal', path: ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts index fc1cc92bbe1cf..6443981b327d9 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts @@ -26,8 +26,10 @@ export const assetCriticalityInternalStatusRoute = ( .get({ access: 'internal', path: ASSET_CRITICALITY_INTERNAL_STATUS_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts index 8c1d94176111c..dccf24d161054 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts @@ -35,8 +35,12 @@ export const assetCriticalityPublicCSVUploadRoute = ( .post({ access: 'public', path: ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, + }, options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], body: { output: 'stream', accepts: 'multipart/form-data', diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts index 488a75c0196ab..8614a2b8e9ad1 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts @@ -31,8 +31,10 @@ export const assetCriticalityPublicUpsertRoute = ( .post({ access: 'public', path: ASSET_CRITICALITY_PUBLIC_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/apply_dataview_indices.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/apply_dataview_indices.ts index 72cd02f273cad..115afdb6b0b3b 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/apply_dataview_indices.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/apply_dataview_indices.ts @@ -20,8 +20,10 @@ export const applyDataViewIndicesEntityEngineRoute = ( .post({ access: 'public', path: '/api/entity_store/engines/apply_dataview_indices', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts index e11c9d3fa7b9d..9e221093d9582 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/delete.ts @@ -28,8 +28,10 @@ export const deleteEntityEngineRoute = ( .delete({ access: 'public', path: '/api/entity_store/engines/{entityType}', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/entities/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/entities/list.ts index 3eefcb7de5752..9f195bb33c8d9 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/entities/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/entities/list.ts @@ -32,8 +32,10 @@ export const listEntitiesRoute = (router: EntityAnalyticsRoutesDeps['router'], l .get({ access: 'public', path: LIST_ENTITIES_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts index 23f013598b476..3ae59d6d748cb 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/get.ts @@ -23,8 +23,10 @@ export const getEntityEngineRoute = ( .get({ access: 'public', path: '/api/entity_store/engines/{entityType}', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts index 3535be022179b..c6ae2f23366a0 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/init.ts @@ -28,8 +28,10 @@ export const initEntityEngineRoute = ( .post({ access: 'public', path: '/api/entity_store/engines/{entityType}/init', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts index 7cec67bcdf5cd..372331cea7087 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/list.ts @@ -22,8 +22,10 @@ export const listEntityEnginesRoute = ( .get({ access: 'public', path: '/api/entity_store/engines', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts index 1872de211cb8f..2985553e874c1 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/start.ts @@ -24,8 +24,10 @@ export const startEntityEngineRoute = ( .post({ access: 'public', path: '/api/entity_store/engines/{entityType}/start', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts index 9ca3cd906f016..24785fbd5c015 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stats.ts @@ -23,8 +23,10 @@ export const getEntityEngineStatsRoute = ( .post({ access: 'public', path: '/api/entity_store/engines/{entityType}/stats', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts index 3ec84e13aa1db..0ba0b008a731c 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/stop.ts @@ -24,8 +24,10 @@ export const stopEntityEngineRoute = ( .post({ access: 'public', path: '/api/entity_store/engines/{entityType}/stop', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.ts index 1776ddcca69b1..473627c9c7b09 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.ts @@ -23,8 +23,10 @@ export const riskEngineCleanupRoute = ( .delete({ access: 'public', path: RISK_ENGINE_CLEANUP_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts index 59b4b4f77537e..fafa887d6fbb4 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts @@ -24,8 +24,10 @@ export const riskEngineDisableRoute = ( .post({ access: 'internal', path: RISK_ENGINE_DISABLE_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts index 24b3c3816440d..ce86ac8f99bd6 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts @@ -24,8 +24,10 @@ export const riskEngineEnableRoute = ( .post({ access: 'internal', path: RISK_ENGINE_ENABLE_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts index 4657d21cbcbe0..67edb78b740c5 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts @@ -26,8 +26,10 @@ export const riskEngineInitRoute = ( .post({ access: 'internal', path: RISK_ENGINE_INIT_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts index f14e06fa72868..307da6980da50 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts @@ -24,8 +24,10 @@ export const riskEnginePrivilegesRoute = ( .get({ access: 'internal', path: RISK_ENGINE_PRIVILEGES_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts index 91f32954f6102..99ec60b281293 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts @@ -27,8 +27,10 @@ export const riskEngineScheduleNowRoute = ( .post({ access: 'public', path: RISK_ENGINE_SCHEDULE_NOW_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts index e300f012b86cf..8073f1222302f 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts @@ -19,8 +19,10 @@ export const riskEngineSettingsRoute = (router: EntityAnalyticsRoutesDeps['route .get({ access: 'internal', path: RISK_ENGINE_SETTINGS_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts index 9b69ddec6b005..5ece4cbf48e43 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts @@ -20,8 +20,10 @@ export const riskEngineStatusRoute = ( .get({ access: 'internal', path: RISK_ENGINE_STATUS_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts index 4b1cf773a572b..8fe611721d323 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts @@ -166,8 +166,10 @@ export const deprecatedRiskScoreEntityCalculationRoute = ( .post({ path: '/api/risk_scores/calculation/entity', access: 'internal', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( @@ -192,8 +194,10 @@ export const riskScoreEntityCalculationRoute = ( .post({ path: RISK_SCORE_ENTITY_CALCULATION_URL, access: 'internal', - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts index e2a1e664c5e76..5ab6791a300c3 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts @@ -30,8 +30,10 @@ export const riskScorePreviewRoute = ( .post({ access: 'internal', path: RISK_SCORE_PREVIEW_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts b/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts index 79eef256f8e93..c53039367dfbe 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/index_status/index.ts @@ -18,8 +18,10 @@ export const getRiskScoreIndexStatusRoute = (router: SecuritySolutionPluginRoute .get({ access: 'internal', path: RISK_SCORE_INDEX_STATUS_API_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts index ef4aacf251ff4..d029d098b03bf 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/indices/create_index_route.ts @@ -19,8 +19,10 @@ export const createEsIndexRoute = (router: SecuritySolutionPluginRouter, logger: .put({ access: 'internal', path: RISK_SCORE_CREATE_INDEX, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts index 407e9e0a8f3e0..4e7f8ed0975f1 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/indices/delete_indices_route.ts @@ -17,8 +17,10 @@ export const deleteEsIndicesRoute = (router: SecuritySolutionPluginRouter) => { .post({ access: 'internal', path: RISK_SCORE_DELETE_INDICES, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts index de6675985fc00..342ae7a0c577b 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/onboarding/routes/install_risk_scores.ts @@ -21,8 +21,10 @@ export const installRiskScoresRoute = (router: SecuritySolutionPluginRouter, log .post({ access: 'internal', path: INTERNAL_RISK_SCORE_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts index 77553eca21d5c..81a7694ba79e9 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_dev_tool_content/routes/read_prebuilt_dev_tool_content_route.ts @@ -53,8 +53,10 @@ export const readPrebuiltDevToolContentRoute = (router: SecuritySolutionPluginRo .get({ access: 'internal', path: DEV_TOOL_PREBUILT_CONTENT, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts index a17669af734fe..2ccccc4bab787 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/create_prebuilt_saved_objects.ts @@ -24,8 +24,10 @@ export const createPrebuiltSavedObjectsRoute = ( .post({ access: 'internal', path: PREBUILT_SAVED_OBJECTS_BULK_CREATE, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts index bd7ae03191ea5..7e772e710cc93 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/prebuilt_saved_objects/routes/delete_prebuilt_saved_objects.ts @@ -21,8 +21,10 @@ export const deletePrebuiltSavedObjectsRoute = (router: SecuritySolutionPluginRo .post({ access: 'internal', path: PREBUILT_SAVED_OBJECTS_BULK_DELETE, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts index 573d1d30bcd28..85cd1fb9d1928 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/create_script_route.ts @@ -18,8 +18,10 @@ export const createStoredScriptRoute = (router: SecuritySolutionPluginRouter, lo .put({ access: 'internal', path: RISK_SCORE_CREATE_STORED_SCRIPT, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts index 0d7ef94be2635..91bf387d55bd0 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_score/stored_scripts/delete_script_route.ts @@ -17,8 +17,10 @@ export const deleteStoredScriptRoute = (router: SecuritySolutionPluginRouter) => .delete({ access: 'internal', path: RISK_SCORE_DELETE_STORED_SCRIPT, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( From e9fd052ef1262990cefa650e4e35cb896085fd5e Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 6 Nov 2024 13:38:31 +0100 Subject: [PATCH 07/53] Code owners - sort generated entries (#198901) ## Summary This PR adds sorting by path to the code owners generation. This improves readability and also avoid problems where ownership entries for nested test plugins (e.g. `x-pack/test/alerting_api_integration/common/plugins/alerts @elastic/response-ops`) are overridden with the empty top level test package entry (`x-pack/test`) when it's placed further down in the code owners file. --- .github/CODEOWNERS | 1082 ++++++++--------- .../src/commands/codeowners_command.ts | 5 + 2 files changed, 546 insertions(+), 541 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 43a315646772d..6dc2aa32a79a9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,126 +5,61 @@ ## `node scripts/generate codeowners`. #### -x-pack/test/alerting_api_integration/common/plugins/aad @elastic/response-ops -x-pack/plugins/actions @elastic/response-ops -x-pack/test/alerting_api_integration/common/plugins/actions_simulators @elastic/response-ops -packages/kbn-actions-types @elastic/response-ops -src/plugins/advanced_settings @elastic/appex-sharedux @elastic/kibana-management -x-pack/packages/kbn-ai-assistant @elastic/search-kibana -x-pack/packages/kbn-ai-assistant-common @elastic/search-kibana -src/plugins/ai_assistant_management/selection @elastic/obs-knowledge-team -x-pack/packages/ml/aiops_change_point_detection @elastic/ml-ui -x-pack/packages/ml/aiops_common @elastic/ml-ui -x-pack/packages/ml/aiops_components @elastic/ml-ui -x-pack/packages/ml/aiops_log_pattern_analysis @elastic/ml-ui -x-pack/packages/ml/aiops_log_rate_analysis @elastic/ml-ui -x-pack/plugins/aiops @elastic/ml-ui -x-pack/packages/ml/aiops_test_utils @elastic/ml-ui -x-pack/test/alerting_api_integration/packages/helpers @elastic/response-ops -x-pack/test/alerting_api_integration/common/plugins/alerts @elastic/response-ops -x-pack/packages/kbn-alerting-comparators @elastic/response-ops -x-pack/examples/alerting_example @elastic/response-ops -x-pack/test/functional_with_es_ssl/plugins/alerts @elastic/response-ops -x-pack/plugins/alerting @elastic/response-ops -x-pack/packages/kbn-alerting-state-types @elastic/response-ops -packages/kbn-alerting-types @elastic/response-ops -packages/kbn-alerts-as-data-utils @elastic/response-ops -packages/kbn-alerts-grouping @elastic/response-ops -x-pack/test/alerting_api_integration/common/plugins/alerts_restricted @elastic/response-ops -packages/kbn-alerts-ui-shared @elastic/response-ops -packages/kbn-ambient-common-types @elastic/kibana-operations -packages/kbn-ambient-ftr-types @elastic/kibana-operations @elastic/appex-qa -packages/kbn-ambient-storybook-types @elastic/kibana-operations -packages/kbn-ambient-ui-types @elastic/kibana-operations -packages/kbn-analytics @elastic/kibana-core -packages/analytics/utils/analytics_collection_utils @elastic/kibana-core -test/analytics/plugins/analytics_ftr_helpers @elastic/kibana-core -test/analytics/plugins/analytics_plugin_a @elastic/kibana-core -packages/kbn-apm-config-loader @elastic/kibana-core @vigneshshanmugam -x-pack/plugins/observability_solution/apm_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team -packages/kbn-apm-data-view @elastic/obs-ux-infra_services-team -x-pack/plugins/observability_solution/apm/ftr_e2e @elastic/obs-ux-infra_services-team -x-pack/plugins/observability_solution/apm @elastic/obs-ux-infra_services-team -packages/kbn-apm-synthtrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team -packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team -packages/kbn-apm-types @elastic/obs-ux-infra_services-team -packages/kbn-apm-utils @elastic/obs-ux-infra_services-team -test/plugin_functional/plugins/app_link_test @elastic/kibana-core -x-pack/test/usage_collection/plugins/application_usage_test @elastic/kibana-core -x-pack/test/security_api_integration/plugins/audit_log @elastic/kibana-security -packages/kbn-avc-banner @elastic/security-defend-workflows -packages/kbn-axe-config @elastic/kibana-qa -packages/kbn-babel-preset @elastic/kibana-operations -packages/kbn-babel-register @elastic/kibana-operations -packages/kbn-babel-transform @elastic/kibana-operations -x-pack/plugins/banners @elastic/appex-sharedux -packages/kbn-bazel-runner @elastic/kibana-operations -packages/kbn-bfetch-error @elastic/appex-sharedux examples/bfetch_explorer @elastic/appex-sharedux -src/plugins/bfetch @elastic/appex-sharedux -packages/kbn-calculate-auto @elastic/obs-ux-management-team -packages/kbn-calculate-width-from-char-count @elastic/kibana-visualizations -x-pack/plugins/canvas @elastic/kibana-presentation -packages/kbn-capture-oas-snapshot-cli @elastic/kibana-core -x-pack/test/cases_api_integration/common/plugins/cases @elastic/response-ops -packages/kbn-cases-components @elastic/response-ops -x-pack/plugins/cases @elastic/response-ops -packages/kbn-cbor @elastic/kibana-operations -packages/kbn-cell-actions @elastic/security-threat-hunting-explore -src/plugins/chart_expressions/common @elastic/kibana-visualizations -packages/kbn-chart-icons @elastic/kibana-visualizations -src/plugins/charts @elastic/kibana-visualizations -packages/kbn-check-mappings-update-cli @elastic/kibana-core -packages/kbn-check-prod-native-modules-cli @elastic/kibana-operations -packages/kbn-ci-stats-core @elastic/kibana-operations -packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations -packages/kbn-ci-stats-reporter @elastic/kibana-operations -packages/kbn-ci-stats-shipper-cli @elastic/kibana-operations -packages/kbn-cli-dev-mode @elastic/kibana-operations +examples/content_management_examples @elastic/appex-sharedux +examples/controls_example @elastic/kibana-presentation +examples/data_view_field_editor_example @elastic/kibana-data-discovery +examples/developer_examples @elastic/appex-sharedux +examples/discover_customization_examples @elastic/kibana-data-discovery +examples/embeddable_examples @elastic/kibana-presentation +examples/error_boundary @elastic/appex-sharedux +examples/eso_model_version_example @elastic/kibana-security +examples/esql_ast_inspector @elastic/kibana-esql +examples/esql_validation_example @elastic/kibana-esql +examples/expressions_explorer @elastic/kibana-visualizations +examples/feature_control_examples @elastic/kibana-security +examples/feature_flags_example @elastic/kibana-core +examples/field_formats_example @elastic/kibana-data-discovery +examples/files_example @elastic/appex-sharedux +examples/grid_example @elastic/kibana-presentation +examples/guided_onboarding_example @elastic/appex-sharedux +examples/hello_world @elastic/kibana-core +examples/locator_examples @elastic/appex-sharedux +examples/locator_explorer @elastic/appex-sharedux +examples/partial_results_example @elastic/kibana-data-discovery +examples/portable_dashboards_example @elastic/kibana-presentation +examples/preboot_example @elastic/kibana-security @elastic/kibana-core +examples/resizable_layout_examples @elastic/kibana-data-discovery +examples/response_stream @elastic/ml-ui +examples/routing_example @elastic/kibana-core +examples/screenshot_mode_example @elastic/appex-sharedux +examples/search_examples @elastic/kibana-data-discovery +examples/share_examples @elastic/appex-sharedux +examples/state_containers_examples @elastic/appex-sharedux +examples/ui_action_examples @elastic/appex-sharedux +examples/ui_actions_explorer @elastic/appex-sharedux +examples/unified_doc_viewer @elastic/kibana-core +examples/unified_field_list_examples @elastic/kibana-data-discovery +examples/user_profile_examples @elastic/kibana-security +examples/v8_profiler_examples @elastic/response-ops +packages/analytics/utils/analytics_collection_utils @elastic/kibana-core packages/cloud @elastic/kibana-core -x-pack/plugins/cloud_integrations/cloud_chat @elastic/kibana-core -x-pack/plugins/cloud_integrations/cloud_data_migration @elastic/kibana-management -x-pack/plugins/cloud_defend @elastic/kibana-cloud-security-posture -x-pack/plugins/cloud_integrations/cloud_experiments @elastic/kibana-core -x-pack/plugins/cloud_integrations/cloud_full_story @elastic/kibana-core -x-pack/test/cloud_integration/plugins/saml_provider @elastic/kibana-core -x-pack/plugins/cloud_integrations/cloud_links @elastic/kibana-core -x-pack/plugins/cloud @elastic/kibana-core -x-pack/packages/kbn-cloud-security-posture/public @elastic/kibana-cloud-security-posture -x-pack/packages/kbn-cloud-security-posture/common @elastic/kibana-cloud-security-posture -x-pack/packages/kbn-cloud-security-posture/graph @elastic/kibana-cloud-security-posture -x-pack/plugins/cloud_security_posture @elastic/kibana-cloud-security-posture -packages/shared-ux/code_editor/impl @elastic/appex-sharedux -packages/shared-ux/code_editor/mocks @elastic/appex-sharedux -packages/kbn-code-owners @elastic/appex-qa -packages/kbn-coloring @elastic/kibana-visualizations -packages/kbn-config @elastic/kibana-core -packages/kbn-config-mocks @elastic/kibana-core -packages/kbn-config-schema @elastic/kibana-core -src/plugins/console @elastic/kibana-management packages/content-management/content_editor @elastic/appex-sharedux packages/content-management/content_insights/content_insights_public @elastic/appex-sharedux packages/content-management/content_insights/content_insights_server @elastic/appex-sharedux -examples/content_management_examples @elastic/appex-sharedux packages/content-management/favorites/favorites_public @elastic/appex-sharedux packages/content-management/favorites/favorites_server @elastic/appex-sharedux -src/plugins/content_management @elastic/appex-sharedux packages/content-management/tabbed_table_list_view @elastic/appex-sharedux packages/content-management/table_list_view @elastic/appex-sharedux packages/content-management/table_list_view_common @elastic/appex-sharedux packages/content-management/table_list_view_table @elastic/appex-sharedux packages/content-management/user_profiles @elastic/appex-sharedux -packages/kbn-content-management-utils @elastic/kibana-data-discovery -examples/controls_example @elastic/kibana-presentation -src/plugins/controls @elastic/kibana-presentation -src/core @elastic/kibana-core packages/core/analytics/core-analytics-browser @elastic/kibana-core packages/core/analytics/core-analytics-browser-internal @elastic/kibana-core packages/core/analytics/core-analytics-browser-mocks @elastic/kibana-core packages/core/analytics/core-analytics-server @elastic/kibana-core packages/core/analytics/core-analytics-server-internal @elastic/kibana-core packages/core/analytics/core-analytics-server-mocks @elastic/kibana-core -test/plugin_functional/plugins/core_app_status @elastic/kibana-core packages/core/application/core-application-browser @elastic/kibana-core packages/core/application/core-application-browser-internal @elastic/kibana-core packages/core/application/core-application-browser-mocks @elastic/kibana-core @@ -191,14 +126,12 @@ packages/core/feature-flags/core-feature-flags-browser-mocks @elastic/kibana-cor packages/core/feature-flags/core-feature-flags-server @elastic/kibana-core packages/core/feature-flags/core-feature-flags-server-internal @elastic/kibana-core packages/core/feature-flags/core-feature-flags-server-mocks @elastic/kibana-core -test/plugin_functional/plugins/core_history_block @elastic/kibana-core packages/core/http/core-http-browser @elastic/kibana-core packages/core/http/core-http-browser-internal @elastic/kibana-core packages/core/http/core-http-browser-mocks @elastic/kibana-core packages/core/http/core-http-common @elastic/kibana-core packages/core/http/core-http-context-server-internal @elastic/kibana-core packages/core/http/core-http-context-server-mocks @elastic/kibana-core -test/plugin_functional/plugins/core_http @elastic/kibana-core packages/core/http/core-http-request-handler-context-server @elastic/kibana-core packages/core/http/core-http-request-handler-context-server-internal @elastic/kibana-core packages/core/http/core-http-resources-server @elastic/kibana-core @@ -248,19 +181,6 @@ packages/core/notifications/core-notifications-browser-mocks @elastic/kibana-cor packages/core/overlays/core-overlays-browser @elastic/kibana-core packages/core/overlays/core-overlays-browser-internal @elastic/kibana-core packages/core/overlays/core-overlays-browser-mocks @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_a @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_appleave @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_b @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_chromeless @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_deep_links @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_deprecations @elastic/kibana-core -test/plugin_functional/plugins/core_dynamic_resolving_a @elastic/kibana-core -test/plugin_functional/plugins/core_dynamic_resolving_b @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_execution_context @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_helpmenu @elastic/kibana-core -test/node_roles_functional/plugins/core_plugin_initializer_context @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_route_timeouts @elastic/kibana-core -test/plugin_functional/plugins/core_plugin_static_assets @elastic/kibana-core packages/core/plugins/core-plugins-base-server-internal @elastic/kibana-core packages/core/plugins/core-plugins-browser @elastic/kibana-core packages/core/plugins/core-plugins-browser-internal @elastic/kibana-core @@ -273,7 +193,6 @@ packages/core/plugins/core-plugins-server-mocks @elastic/kibana-core packages/core/preboot/core-preboot-server @elastic/kibana-core packages/core/preboot/core-preboot-server-internal @elastic/kibana-core packages/core/preboot/core-preboot-server-mocks @elastic/kibana-core -test/plugin_functional/plugins/core_provider_plugin @elastic/kibana-core packages/core/rendering/core-rendering-browser-internal @elastic/kibana-core packages/core/rendering/core-rendering-browser-mocks @elastic/kibana-core packages/core/rendering/core-rendering-server-internal @elastic/kibana-core @@ -340,32 +259,6 @@ packages/core/user-profile/core-user-profile-server-mocks @elastic/kibana-core packages/core/user-settings/core-user-settings-server @elastic/kibana-security packages/core/user-settings/core-user-settings-server-internal @elastic/kibana-security packages/core/user-settings/core-user-settings-server-mocks @elastic/kibana-security -x-pack/plugins/cross_cluster_replication @elastic/kibana-management -packages/kbn-crypto @elastic/kibana-security -packages/kbn-crypto-browser @elastic/kibana-core -x-pack/plugins/custom_branding @elastic/appex-sharedux -packages/kbn-custom-icons @elastic/obs-ux-logs-team -packages/kbn-custom-integrations @elastic/obs-ux-logs-team -src/plugins/custom_integrations @elastic/fleet -packages/kbn-cypress-config @elastic/kibana-operations -x-pack/plugins/dashboard_enhanced @elastic/kibana-presentation -src/plugins/dashboard @elastic/kibana-presentation -x-pack/packages/kbn-data-forge @elastic/obs-ux-management-team -src/plugins/data @elastic/kibana-visualizations @elastic/kibana-data-discovery -x-pack/plugins/data_quality @elastic/obs-ux-logs-team -test/plugin_functional/plugins/data_search @elastic/kibana-data-discovery -packages/kbn-data-service @elastic/kibana-visualizations @elastic/kibana-data-discovery -packages/kbn-data-stream-adapter @elastic/security-threat-hunting-explore -x-pack/plugins/data_usage @elastic/obs-ai-assistant @elastic/security-solution -src/plugins/data_view_editor @elastic/kibana-data-discovery -examples/data_view_field_editor_example @elastic/kibana-data-discovery -src/plugins/data_view_field_editor @elastic/kibana-data-discovery -src/plugins/data_view_management @elastic/kibana-data-discovery -packages/kbn-data-view-utils @elastic/kibana-data-discovery -src/plugins/data_views @elastic/kibana-data-discovery -x-pack/plugins/data_visualizer @elastic/ml-ui -x-pack/plugins/observability_solution/dataset_quality @elastic/obs-ux-logs-team -packages/kbn-datemath @elastic/kibana-data-discovery packages/deeplinks/analytics @elastic/kibana-data-discovery @elastic/kibana-presentation @elastic/kibana-visualizations packages/deeplinks/devtools @elastic/kibana-management packages/deeplinks/fleet @elastic/fleet @@ -379,167 +272,115 @@ packages/default-nav/analytics @elastic/kibana-data-discovery @elastic/kibana-pr packages/default-nav/devtools @elastic/kibana-management packages/default-nav/management @elastic/kibana-management packages/default-nav/ml @elastic/ml-ui +packages/home/sample_data_card @elastic/appex-sharedux +packages/home/sample_data_tab @elastic/appex-sharedux +packages/home/sample_data_types @elastic/appex-sharedux +packages/kbn-actions-types @elastic/response-ops +packages/kbn-alerting-types @elastic/response-ops +packages/kbn-alerts-as-data-utils @elastic/response-ops +packages/kbn-alerts-grouping @elastic/response-ops +packages/kbn-alerts-ui-shared @elastic/response-ops +packages/kbn-ambient-common-types @elastic/kibana-operations +packages/kbn-ambient-ftr-types @elastic/kibana-operations @elastic/appex-qa +packages/kbn-ambient-storybook-types @elastic/kibana-operations +packages/kbn-ambient-ui-types @elastic/kibana-operations +packages/kbn-analytics @elastic/kibana-core +packages/kbn-apm-config-loader @elastic/kibana-core @vigneshshanmugam +packages/kbn-apm-data-view @elastic/obs-ux-infra_services-team +packages/kbn-apm-synthtrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team +packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team +packages/kbn-apm-types @elastic/obs-ux-infra_services-team +packages/kbn-apm-utils @elastic/obs-ux-infra_services-team +packages/kbn-avc-banner @elastic/security-defend-workflows +packages/kbn-axe-config @elastic/kibana-qa +packages/kbn-babel-preset @elastic/kibana-operations +packages/kbn-babel-register @elastic/kibana-operations +packages/kbn-babel-transform @elastic/kibana-operations +packages/kbn-bazel-runner @elastic/kibana-operations +packages/kbn-bfetch-error @elastic/appex-sharedux +packages/kbn-calculate-auto @elastic/obs-ux-management-team +packages/kbn-calculate-width-from-char-count @elastic/kibana-visualizations +packages/kbn-capture-oas-snapshot-cli @elastic/kibana-core +packages/kbn-cases-components @elastic/response-ops +packages/kbn-cbor @elastic/kibana-operations +packages/kbn-cell-actions @elastic/security-threat-hunting-explore +packages/kbn-chart-icons @elastic/kibana-visualizations +packages/kbn-check-mappings-update-cli @elastic/kibana-core +packages/kbn-check-prod-native-modules-cli @elastic/kibana-operations +packages/kbn-ci-stats-core @elastic/kibana-operations +packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations +packages/kbn-ci-stats-reporter @elastic/kibana-operations +packages/kbn-ci-stats-shipper-cli @elastic/kibana-operations +packages/kbn-cli-dev-mode @elastic/kibana-operations +packages/kbn-code-owners @elastic/appex-qa +packages/kbn-coloring @elastic/kibana-visualizations +packages/kbn-config @elastic/kibana-core +packages/kbn-config-mocks @elastic/kibana-core +packages/kbn-config-schema @elastic/kibana-core +packages/kbn-content-management-utils @elastic/kibana-data-discovery +packages/kbn-crypto @elastic/kibana-security +packages/kbn-crypto-browser @elastic/kibana-core +packages/kbn-custom-icons @elastic/obs-ux-logs-team +packages/kbn-custom-integrations @elastic/obs-ux-logs-team +packages/kbn-cypress-config @elastic/kibana-operations +packages/kbn-data-service @elastic/kibana-visualizations @elastic/kibana-data-discovery +packages/kbn-data-stream-adapter @elastic/security-threat-hunting-explore +packages/kbn-data-view-utils @elastic/kibana-data-discovery +packages/kbn-datemath @elastic/kibana-data-discovery packages/kbn-dev-cli-errors @elastic/kibana-operations packages/kbn-dev-cli-runner @elastic/kibana-operations packages/kbn-dev-proc-runner @elastic/kibana-operations -src/plugins/dev_tools @elastic/kibana-management packages/kbn-dev-utils @elastic/kibana-operations -examples/developer_examples @elastic/appex-sharedux packages/kbn-discover-contextual-components @elastic/obs-ux-logs-team @elastic/kibana-data-discovery -examples/discover_customization_examples @elastic/kibana-data-discovery -x-pack/plugins/discover_enhanced @elastic/kibana-data-discovery -src/plugins/discover @elastic/kibana-data-discovery -src/plugins/discover_shared @elastic/kibana-data-discovery @elastic/obs-ux-logs-team packages/kbn-discover-utils @elastic/kibana-data-discovery packages/kbn-doc-links @elastic/docs packages/kbn-docs-utils @elastic/kibana-operations packages/kbn-dom-drag-drop @elastic/kibana-visualizations @elastic/kibana-data-discovery packages/kbn-ebt-tools @elastic/kibana-core -x-pack/packages/security-solution/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore -x-pack/plugins/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore packages/kbn-elastic-agent-utils @elastic/obs-ux-logs-team -x-pack/packages/kbn-elastic-assistant @elastic/security-generative-ai -x-pack/packages/kbn-elastic-assistant-common @elastic/security-generative-ai -x-pack/plugins/elastic_assistant @elastic/security-generative-ai -test/plugin_functional/plugins/elasticsearch_client_plugin @elastic/kibana-core -x-pack/test/plugin_api_integration/plugins/elasticsearch_client @elastic/kibana-core -x-pack/plugins/embeddable_enhanced @elastic/kibana-presentation -examples/embeddable_examples @elastic/kibana-presentation -src/plugins/embeddable @elastic/kibana-presentation -x-pack/examples/embedded_lens_example @elastic/kibana-visualizations -x-pack/plugins/encrypted_saved_objects @elastic/kibana-security -x-pack/plugins/enterprise_search @elastic/search-kibana -x-pack/plugins/observability_solution/entities_data_access @elastic/obs-entities -x-pack/packages/kbn-entities-schema @elastic/obs-entities -x-pack/test/api_integration/apis/entity_manager/fixture_plugin @elastic/obs-entities -x-pack/plugins/entity_manager @elastic/obs-entities -examples/error_boundary @elastic/appex-sharedux packages/kbn-es @elastic/kibana-operations packages/kbn-es-archiver @elastic/kibana-operations @elastic/appex-qa packages/kbn-es-errors @elastic/kibana-core packages/kbn-es-query @elastic/kibana-data-discovery packages/kbn-es-types @elastic/kibana-core @elastic/obs-knowledge-team -src/plugins/es_ui_shared @elastic/kibana-management packages/kbn-eslint-config @elastic/kibana-operations packages/kbn-eslint-plugin-disable @elastic/kibana-operations packages/kbn-eslint-plugin-eslint @elastic/kibana-operations packages/kbn-eslint-plugin-i18n @elastic/obs-knowledge-team @elastic/kibana-operations packages/kbn-eslint-plugin-imports @elastic/kibana-operations packages/kbn-eslint-plugin-telemetry @elastic/obs-knowledge-team -examples/eso_model_version_example @elastic/kibana-security -x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin @elastic/kibana-security -src/plugins/esql @elastic/kibana-esql packages/kbn-esql-ast @elastic/kibana-esql -examples/esql_ast_inspector @elastic/kibana-esql -src/plugins/esql_datagrid @elastic/kibana-esql packages/kbn-esql-editor @elastic/kibana-esql packages/kbn-esql-utils @elastic/kibana-esql packages/kbn-esql-validation-autocomplete @elastic/kibana-esql -examples/esql_validation_example @elastic/kibana-esql -test/plugin_functional/plugins/eui_provider_dev_warning @elastic/appex-sharedux packages/kbn-event-annotation-common @elastic/kibana-visualizations packages/kbn-event-annotation-components @elastic/kibana-visualizations -src/plugins/event_annotation_listing @elastic/kibana-visualizations -src/plugins/event_annotation @elastic/kibana-visualizations -x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops -x-pack/plugins/event_log @elastic/response-ops packages/kbn-expandable-flyout @elastic/security-threat-hunting-investigations packages/kbn-expect @elastic/kibana-operations @elastic/appex-qa -x-pack/examples/exploratory_view_example @elastic/obs-ux-infra_services-team -x-pack/plugins/observability_solution/exploratory_view @elastic/obs-ux-management-team -src/plugins/expression_error @elastic/kibana-presentation -src/plugins/chart_expressions/expression_gauge @elastic/kibana-visualizations -src/plugins/chart_expressions/expression_heatmap @elastic/kibana-visualizations -src/plugins/expression_image @elastic/kibana-presentation -src/plugins/chart_expressions/expression_legacy_metric @elastic/kibana-visualizations -src/plugins/expression_metric @elastic/kibana-presentation -src/plugins/chart_expressions/expression_metric @elastic/kibana-visualizations -src/plugins/chart_expressions/expression_partition_vis @elastic/kibana-visualizations -src/plugins/expression_repeat_image @elastic/kibana-presentation -src/plugins/expression_reveal_image @elastic/kibana-presentation -src/plugins/expression_shape @elastic/kibana-presentation -src/plugins/chart_expressions/expression_tagcloud @elastic/kibana-visualizations -src/plugins/chart_expressions/expression_xy @elastic/kibana-visualizations -examples/expressions_explorer @elastic/kibana-visualizations -src/plugins/expressions @elastic/kibana-visualizations packages/kbn-failed-test-reporter-cli @elastic/kibana-operations @elastic/appex-qa -examples/feature_control_examples @elastic/kibana-security -examples/feature_flags_example @elastic/kibana-core -x-pack/test/plugin_api_integration/plugins/feature_usage_test @elastic/kibana-security -x-pack/plugins/features @elastic/kibana-core -x-pack/test/security_api_integration/plugins/features_provider @elastic/kibana-security -x-pack/test/functional_execution_context/plugins/alerts @elastic/kibana-core -examples/field_formats_example @elastic/kibana-data-discovery -src/plugins/field_formats @elastic/kibana-data-discovery packages/kbn-field-types @elastic/kibana-data-discovery packages/kbn-field-utils @elastic/kibana-data-discovery -x-pack/plugins/fields_metadata @elastic/obs-ux-logs-team -x-pack/plugins/file_upload @elastic/kibana-presentation @elastic/ml-ui -examples/files_example @elastic/appex-sharedux -src/plugins/files_management @elastic/appex-sharedux -src/plugins/files @elastic/appex-sharedux packages/kbn-find-used-node-modules @elastic/kibana-operations -x-pack/plugins/fleet @elastic/fleet packages/kbn-flot-charts @elastic/kibana-presentation @elastic/stack-monitoring -x-pack/test/ui_capabilities/common/plugins/foo_plugin @elastic/kibana-security packages/kbn-formatters @elastic/obs-ux-logs-team -src/plugins/ftr_apis @elastic/kibana-core packages/kbn-ftr-common-functional-services @elastic/kibana-operations @elastic/appex-qa packages/kbn-ftr-common-functional-ui-services @elastic/appex-qa packages/kbn-ftr-screenshot-filename @elastic/kibana-operations @elastic/appex-qa -x-pack/test/functional_with_es_ssl/plugins/cases @elastic/response-ops -x-pack/examples/gen_ai_streaming_response_example @elastic/response-ops packages/kbn-generate @elastic/kibana-operations packages/kbn-generate-console-definitions @elastic/kibana-management packages/kbn-generate-csv @elastic/appex-sharedux packages/kbn-get-repo-files @elastic/kibana-operations -x-pack/plugins/global_search_bar @elastic/appex-sharedux -x-pack/plugins/global_search @elastic/appex-sharedux -x-pack/plugins/global_search_providers @elastic/appex-sharedux -x-pack/test/plugin_functional/plugins/global_search_test @elastic/kibana-core -x-pack/plugins/graph @elastic/kibana-visualizations -examples/grid_example @elastic/kibana-presentation packages/kbn-grid-layout @elastic/kibana-presentation -x-pack/plugins/grokdebugger @elastic/kibana-management packages/kbn-grouping @elastic/response-ops packages/kbn-guided-onboarding @elastic/appex-sharedux -examples/guided_onboarding_example @elastic/appex-sharedux -src/plugins/guided_onboarding @elastic/appex-sharedux packages/kbn-handlebars @elastic/kibana-security packages/kbn-hapi-mocks @elastic/kibana-core -test/plugin_functional/plugins/hardening @elastic/kibana-security packages/kbn-health-gateway-server @elastic/kibana-core -examples/hello_world @elastic/kibana-core -src/plugins/home @elastic/kibana-core -packages/home/sample_data_card @elastic/appex-sharedux -packages/home/sample_data_tab @elastic/appex-sharedux -packages/home/sample_data_types @elastic/appex-sharedux packages/kbn-i18n @elastic/kibana-core packages/kbn-i18n-react @elastic/kibana-core -x-pack/test/functional_embedded/plugins/iframe_embedded @elastic/kibana-core -src/plugins/image_embeddable @elastic/appex-sharedux packages/kbn-import-locator @elastic/kibana-operations packages/kbn-import-resolver @elastic/kibana-operations -x-pack/plugins/index_lifecycle_management @elastic/kibana-management -x-pack/plugins/index_management @elastic/kibana-management -x-pack/packages/index-management/index_management_shared_types @elastic/kibana-management -test/plugin_functional/plugins/index_patterns @elastic/kibana-data-discovery -x-pack/packages/ml/inference_integration_flyout @elastic/ml-ui -x-pack/packages/ai-infra/inference-common @elastic/appex-ai-infra -x-pack/plugins/inference @elastic/appex-ai-infra -x-pack/packages/kbn-infra-forge @elastic/obs-ux-management-team -x-pack/plugins/observability_solution/infra @elastic/obs-ux-logs-team @elastic/obs-ux-infra_services-team -x-pack/plugins/ingest_pipelines @elastic/kibana-management -src/plugins/input_control_vis @elastic/kibana-presentation -src/plugins/inspector @elastic/kibana-presentation -x-pack/plugins/integration_assistant @elastic/security-scalability -src/plugins/interactive_setup @elastic/kibana-security -test/interactive_setup_api_integration/plugins/test_endpoints @elastic/kibana-security packages/kbn-interpreter @elastic/kibana-visualizations -x-pack/plugins/observability_solution/inventory/e2e @elastic/obs-ux-infra_services-team -x-pack/plugins/observability_solution/inventory @elastic/obs-ux-infra_services-team -x-pack/plugins/observability_solution/investigate_app @elastic/obs-ux-management-team -x-pack/plugins/observability_solution/investigate @elastic/obs-ux-management-team packages/kbn-investigation-shared @elastic/obs-ux-management-team packages/kbn-io-ts-utils @elastic/obs-knowledge-team packages/kbn-ipynb @elastic/search-kibana @@ -547,137 +388,42 @@ packages/kbn-item-buffer @elastic/appex-sharedux packages/kbn-jest-serializers @elastic/kibana-operations packages/kbn-journeys @elastic/kibana-operations @elastic/appex-qa packages/kbn-json-ast @elastic/kibana-operations -x-pack/packages/ml/json_schemas @elastic/ml-ui -test/health_gateway/plugins/status @elastic/kibana-core -test/plugin_functional/plugins/kbn_sample_panel_action @elastic/appex-sharedux -test/plugin_functional/plugins/kbn_top_nav @elastic/kibana-core -test/plugin_functional/plugins/kbn_tp_custom_visualizations @elastic/kibana-visualizations -test/interpreter_functional/plugins/kbn_tp_run_pipeline @elastic/kibana-core -x-pack/test/functional_cors/plugins/kibana_cors_test @elastic/kibana-security packages/kbn-kibana-manifest-schema @elastic/kibana-operations -src/plugins/kibana_overview @elastic/appex-sharedux -src/plugins/kibana_react @elastic/appex-sharedux -src/plugins/kibana_usage_collection @elastic/kibana-core -src/plugins/kibana_utils @elastic/appex-sharedux -x-pack/plugins/kubernetes_security @elastic/kibana-cloud-security-posture -x-pack/packages/kbn-langchain @elastic/security-generative-ai packages/kbn-language-documentation @elastic/kibana-esql -x-pack/examples/lens_config_builder_example @elastic/kibana-visualizations packages/kbn-lens-embeddable-utils @elastic/obs-ux-infra_services-team @elastic/kibana-visualizations packages/kbn-lens-formula-docs @elastic/kibana-visualizations -x-pack/examples/lens_embeddable_inline_editing_example @elastic/kibana-visualizations -x-pack/plugins/lens @elastic/kibana-visualizations -x-pack/plugins/license_api_guard @elastic/kibana-management -x-pack/plugins/license_management @elastic/kibana-management -x-pack/plugins/licensing @elastic/kibana-core -src/plugins/links @elastic/kibana-presentation packages/kbn-lint-packages-cli @elastic/kibana-operations packages/kbn-lint-ts-projects-cli @elastic/kibana-operations -x-pack/plugins/lists @elastic/security-detection-engine -examples/locator_examples @elastic/appex-sharedux -examples/locator_explorer @elastic/appex-sharedux packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core -x-pack/plugins/observability_solution/logs_data_access @elastic/obs-knowledge-team @elastic/obs-ux-logs-team -x-pack/plugins/observability_solution/logs_explorer @elastic/obs-ux-logs-team -x-pack/plugins/observability_solution/logs_shared @elastic/obs-ux-logs-team -x-pack/plugins/logstash @elastic/logstash packages/kbn-managed-content-badge @elastic/kibana-visualizations packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations packages/kbn-management/cards_navigation @elastic/kibana-management -src/plugins/management @elastic/kibana-management packages/kbn-management/settings/application @elastic/kibana-management packages/kbn-management/settings/components/field_category @elastic/kibana-management packages/kbn-management/settings/components/field_input @elastic/kibana-management packages/kbn-management/settings/components/field_row @elastic/kibana-management packages/kbn-management/settings/components/form @elastic/kibana-management packages/kbn-management/settings/field_definition @elastic/kibana-management -packages/kbn-management/settings/setting_ids @elastic/appex-sharedux @elastic/kibana-management packages/kbn-management/settings/section_registry @elastic/appex-sharedux @elastic/kibana-management +packages/kbn-management/settings/setting_ids @elastic/appex-sharedux @elastic/kibana-management packages/kbn-management/settings/types @elastic/kibana-management packages/kbn-management/settings/utilities @elastic/kibana-management packages/kbn-management/storybook/config @elastic/kibana-management -test/plugin_functional/plugins/management_test_plugin @elastic/kibana-management packages/kbn-manifest @elastic/kibana-core packages/kbn-mapbox-gl @elastic/kibana-presentation -x-pack/examples/third_party_maps_source_example @elastic/kibana-presentation -src/plugins/maps_ems @elastic/kibana-presentation -x-pack/plugins/maps @elastic/kibana-presentation -x-pack/packages/maps/vector_tile_utils @elastic/kibana-presentation -x-pack/plugins/observability_solution/metrics_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team -x-pack/packages/ml/agg_utils @elastic/ml-ui -x-pack/packages/ml/anomaly_utils @elastic/ml-ui -x-pack/packages/ml/cancellable_search @elastic/ml-ui -x-pack/packages/ml/category_validator @elastic/ml-ui -x-pack/packages/ml/chi2test @elastic/ml-ui -x-pack/packages/ml/creation_wizard_utils @elastic/ml-ui -x-pack/packages/ml/data_frame_analytics_utils @elastic/ml-ui -x-pack/packages/ml/data_grid @elastic/ml-ui -x-pack/packages/ml/data_view_utils @elastic/ml-ui -x-pack/packages/ml/date_picker @elastic/ml-ui -x-pack/packages/ml/date_utils @elastic/ml-ui -x-pack/packages/ml/error_utils @elastic/ml-ui -x-pack/packages/ml/field_stats_flyout @elastic/ml-ui -x-pack/packages/ml/in_memory_table @elastic/ml-ui -x-pack/packages/ml/is_defined @elastic/ml-ui -x-pack/packages/ml/is_populated_object @elastic/ml-ui -x-pack/packages/ml/kibana_theme @elastic/ml-ui -x-pack/packages/ml/local_storage @elastic/ml-ui -x-pack/packages/ml/nested_property @elastic/ml-ui -x-pack/packages/ml/number_utils @elastic/ml-ui -x-pack/packages/ml/parse_interval @elastic/ml-ui -x-pack/plugins/ml @elastic/ml-ui -x-pack/packages/ml/query_utils @elastic/ml-ui -x-pack/packages/ml/random_sampler_utils @elastic/ml-ui -x-pack/packages/ml/response_stream @elastic/ml-ui -x-pack/packages/ml/route_utils @elastic/ml-ui -x-pack/packages/ml/runtime_field_utils @elastic/ml-ui -x-pack/packages/ml/string_hash @elastic/ml-ui -x-pack/packages/ml/time_buckets @elastic/ml-ui -x-pack/packages/ml/trained_models_utils @elastic/ml-ui -x-pack/packages/ml/ui_actions @elastic/ml-ui -x-pack/packages/ml/url_state @elastic/ml-ui -x-pack/packages/ml/validators @elastic/ml-ui packages/kbn-mock-idp-plugin @elastic/kibana-security packages/kbn-mock-idp-utils @elastic/kibana-security packages/kbn-monaco @elastic/appex-sharedux -x-pack/plugins/monitoring_collection @elastic/stack-monitoring -x-pack/plugins/monitoring @elastic/stack-monitoring -src/plugins/navigation @elastic/appex-sharedux -src/plugins/newsfeed @elastic/kibana-core -test/common/plugins/newsfeed @elastic/kibana-core -src/plugins/no_data_page @elastic/appex-sharedux -x-pack/plugins/notifications @elastic/appex-sharedux packages/kbn-object-versioning @elastic/appex-sharedux packages/kbn-object-versioning-utils @elastic/appex-sharedux -x-pack/plugins/observability_solution/observability_ai_assistant_app @elastic/obs-ai-assistant -x-pack/plugins/observability_solution/observability_ai_assistant_management @elastic/obs-ai-assistant -x-pack/plugins/observability_solution/observability_ai_assistant @elastic/obs-ai-assistant -x-pack/packages/observability/alert_details @elastic/obs-ux-management-team -x-pack/packages/observability/alerting_rule_utils @elastic/obs-ux-management-team -x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team -x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops -x-pack/packages/observability/get_padded_alert_time_range_util @elastic/obs-ux-management-team -x-pack/plugins/observability_solution/observability_logs_explorer @elastic/obs-ux-logs-team -x-pack/packages/observability/logs_overview @elastic/obs-ux-logs-team -x-pack/plugins/observability_solution/observability_onboarding/e2e @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team -x-pack/plugins/observability_solution/observability_onboarding @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team -x-pack/plugins/observability_solution/observability @elastic/obs-ux-management-team -x-pack/plugins/observability_solution/observability_shared @elastic/observability-ui -x-pack/packages/observability/synthetics_test_data @elastic/obs-ux-management-team -x-pack/packages/observability/observability_utils @elastic/observability-ui -x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security -test/common/plugins/otel_metrics @elastic/obs-ux-infra_services-team packages/kbn-openapi-bundler @elastic/security-detection-rule-management packages/kbn-openapi-common @elastic/security-detection-rule-management packages/kbn-openapi-generator @elastic/security-detection-rule-management packages/kbn-optimizer @elastic/kibana-operations packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations packages/kbn-osquery-io-ts-types @elastic/security-asset-management -x-pack/plugins/osquery @elastic/security-defend-workflows -examples/partial_results_example @elastic/kibana-data-discovery -x-pack/plugins/painless_lab @elastic/kibana-management packages/kbn-panel-loader @elastic/kibana-presentation packages/kbn-peggy @elastic/kibana-operations packages/kbn-peggy-loader @elastic/kibana-operations @@ -686,28 +432,10 @@ packages/kbn-picomatcher @elastic/kibana-operations packages/kbn-plugin-check @elastic/appex-sharedux packages/kbn-plugin-generator @elastic/kibana-operations packages/kbn-plugin-helpers @elastic/kibana-operations -examples/portable_dashboards_example @elastic/kibana-presentation -examples/preboot_example @elastic/kibana-security @elastic/kibana-core -packages/presentation/presentation_containers @elastic/kibana-presentation -src/plugins/presentation_panel @elastic/kibana-presentation -packages/presentation/presentation_publishing @elastic/kibana-presentation -src/plugins/presentation_util @elastic/kibana-presentation -x-pack/packages/ai-infra/product-doc-artifact-builder @elastic/appex-ai-infra -x-pack/plugins/observability_solution/profiling_data_access @elastic/obs-ux-infra_services-team -x-pack/plugins/observability_solution/profiling @elastic/obs-ux-infra_services-team packages/kbn-profiling-utils @elastic/obs-ux-infra_services-team -x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations packages/kbn-react-field @elastic/kibana-data-discovery packages/kbn-react-hooks @elastic/obs-ux-logs-team -packages/react/kibana_context/common @elastic/appex-sharedux -packages/react/kibana_context/render @elastic/appex-sharedux -packages/react/kibana_context/root @elastic/appex-sharedux -packages/react/kibana_context/styled @elastic/appex-sharedux -packages/react/kibana_context/theme @elastic/appex-sharedux -packages/react/kibana_mount @elastic/appex-sharedux packages/kbn-recently-accessed @elastic/appex-sharedux -x-pack/plugins/remote_clusters @elastic/kibana-management -test/plugin_functional/plugins/rendering_plugin @elastic/kibana-core packages/kbn-repo-file-maps @elastic/kibana-operations packages/kbn-repo-info @elastic/kibana-operations packages/kbn-repo-linter @elastic/kibana-operations @@ -716,97 +444,35 @@ packages/kbn-repo-path @elastic/kibana-operations packages/kbn-repo-source-classifier @elastic/kibana-operations packages/kbn-repo-source-classifier-cli @elastic/kibana-operations packages/kbn-reporting/common @elastic/appex-sharedux -packages/kbn-reporting/get_csv_panel_actions @elastic/appex-sharedux packages/kbn-reporting/export_types/csv @elastic/appex-sharedux packages/kbn-reporting/export_types/csv_common @elastic/appex-sharedux packages/kbn-reporting/export_types/pdf @elastic/appex-sharedux packages/kbn-reporting/export_types/pdf_common @elastic/appex-sharedux packages/kbn-reporting/export_types/png @elastic/appex-sharedux packages/kbn-reporting/export_types/png_common @elastic/appex-sharedux +packages/kbn-reporting/get_csv_panel_actions @elastic/appex-sharedux packages/kbn-reporting/mocks_server @elastic/appex-sharedux -x-pack/plugins/reporting @elastic/appex-sharedux packages/kbn-reporting/public @elastic/appex-sharedux packages/kbn-reporting/server @elastic/appex-sharedux packages/kbn-resizable-layout @elastic/kibana-data-discovery -examples/resizable_layout_examples @elastic/kibana-data-discovery -x-pack/test/plugin_functional/plugins/resolver_test @elastic/security-solution -packages/response-ops/feature_flag_service @elastic/response-ops -packages/response-ops/rule_params @elastic/response-ops -examples/response_stream @elastic/ml-ui packages/kbn-rison @elastic/kibana-operations -x-pack/packages/rollup @elastic/kibana-management -x-pack/plugins/rollup @elastic/kibana-management packages/kbn-router-to-openapispec @elastic/kibana-core packages/kbn-router-utils @elastic/obs-ux-logs-team -examples/routing_example @elastic/kibana-core packages/kbn-rrule @elastic/response-ops packages/kbn-rule-data-utils @elastic/security-detections-response @elastic/response-ops @elastic/obs-ux-management-team -x-pack/plugins/rule_registry @elastic/response-ops @elastic/obs-ux-management-team -x-pack/plugins/runtime_fields @elastic/kibana-management packages/kbn-safer-lodash-set @elastic/kibana-security -x-pack/test/security_api_integration/plugins/saml_provider @elastic/kibana-security -x-pack/test/plugin_api_integration/plugins/sample_task_plugin @elastic/response-ops -x-pack/test/task_manager_claimer_update_by_query/plugins/sample_task_plugin_mget @elastic/response-ops -test/plugin_functional/plugins/saved_object_export_transforms @elastic/kibana-core -test/plugin_functional/plugins/saved_object_import_warnings @elastic/kibana-core -x-pack/test/saved_object_api_integration/common/plugins/saved_object_test_plugin @elastic/kibana-security -src/plugins/saved_objects_finder @elastic/kibana-data-discovery -test/plugin_functional/plugins/saved_objects_hidden_from_http_apis_type @elastic/kibana-core -test/plugin_functional/plugins/saved_objects_hidden_type @elastic/kibana-core -src/plugins/saved_objects_management @elastic/kibana-core -src/plugins/saved_objects @elastic/appex-sharedux packages/kbn-saved-objects-settings @elastic/appex-sharedux -src/plugins/saved_objects_tagging_oss @elastic/appex-sharedux -x-pack/plugins/saved_objects_tagging @elastic/appex-sharedux -src/plugins/saved_search @elastic/kibana-data-discovery -examples/screenshot_mode_example @elastic/appex-sharedux -src/plugins/screenshot_mode @elastic/appex-sharedux -x-pack/examples/screenshotting_example @elastic/appex-sharedux -x-pack/plugins/screenshotting @elastic/kibana-reporting-services packages/kbn-screenshotting-server @elastic/appex-sharedux packages/kbn-search-api-keys-components @elastic/search-kibana packages/kbn-search-api-keys-server @elastic/search-kibana packages/kbn-search-api-panels @elastic/search-kibana -x-pack/plugins/search_assistant @elastic/search-kibana packages/kbn-search-connectors @elastic/search-kibana -x-pack/plugins/search_connectors @elastic/search-kibana packages/kbn-search-errors @elastic/kibana-data-discovery -examples/search_examples @elastic/kibana-data-discovery -x-pack/plugins/search_homepage @elastic/search-kibana packages/kbn-search-index-documents @elastic/search-kibana -x-pack/plugins/search_indices @elastic/search-kibana -x-pack/plugins/search_inference_endpoints @elastic/search-kibana -x-pack/plugins/search_notebooks @elastic/search-kibana -x-pack/plugins/search_playground @elastic/search-kibana packages/kbn-search-response-warnings @elastic/kibana-data-discovery -x-pack/packages/search/shared_ui @elastic/search-kibana packages/kbn-search-types @elastic/kibana-data-discovery -x-pack/plugins/searchprofiler @elastic/kibana-management -x-pack/test/security_api_integration/packages/helpers @elastic/kibana-security -x-pack/packages/security/api_key_management @elastic/kibana-security -x-pack/packages/security/authorization_core @elastic/kibana-security -x-pack/packages/security/authorization_core_common @elastic/kibana-security -x-pack/packages/security/form_components @elastic/kibana-security packages/kbn-security-hardening @elastic/kibana-security -x-pack/plugins/security @elastic/kibana-security -x-pack/packages/security/plugin_types_common @elastic/kibana-security -x-pack/packages/security/plugin_types_public @elastic/kibana-security -x-pack/packages/security/plugin_types_server @elastic/kibana-security -x-pack/packages/security/role_management_model @elastic/kibana-security -x-pack/packages/security-solution/distribution_bar @elastic/kibana-cloud-security-posture -x-pack/plugins/security_solution_ess @elastic/security-solution -x-pack/packages/security-solution/features @elastic/security-threat-hunting-explore -x-pack/test/cases_api_integration/common/plugins/security_solution @elastic/response-ops -x-pack/packages/security-solution/navigation @elastic/security-threat-hunting-explore -x-pack/plugins/security_solution @elastic/security-solution -x-pack/plugins/security_solution_serverless @elastic/security-solution -x-pack/packages/security-solution/side_nav @elastic/security-threat-hunting-explore -x-pack/packages/security-solution/storybook/config @elastic/security-threat-hunting-explore -x-pack/packages/security-solution/upselling @elastic/security-threat-hunting-explore -x-pack/test/security_functional/plugins/test_endpoints @elastic/kibana-security -x-pack/packages/security/ui_components @elastic/kibana-security packages/kbn-securitysolution-autocomplete @elastic/security-detection-engine -x-pack/packages/security-solution/data_table @elastic/security-threat-hunting-investigations packages/kbn-securitysolution-ecs @elastic/security-threat-hunting-explore packages/kbn-securitysolution-endpoint-exceptions-common @elastic/security-detection-engine packages/kbn-securitysolution-es-utils @elastic/security-detection-engine @@ -829,37 +495,90 @@ packages/kbn-server-http-tools @elastic/kibana-core packages/kbn-server-route-repository @elastic/obs-knowledge-team packages/kbn-server-route-repository-client @elastic/obs-knowledge-team packages/kbn-server-route-repository-utils @elastic/obs-knowledge-team -x-pack/plugins/serverless @elastic/appex-sharedux +packages/kbn-set-map @elastic/kibana-operations +packages/kbn-shared-svg @elastic/obs-ux-infra_services-team +packages/kbn-shared-ux-utility @elastic/appex-sharedux +packages/kbn-some-dev-log @elastic/kibana-operations +packages/kbn-sort-package-json @elastic/kibana-operations +packages/kbn-sort-predicates @elastic/kibana-visualizations +packages/kbn-sse-utils @elastic/obs-knowledge-team +packages/kbn-sse-utils-client @elastic/obs-knowledge-team +packages/kbn-sse-utils-server @elastic/obs-knowledge-team +packages/kbn-std @elastic/kibana-core +packages/kbn-stdio-dev-helpers @elastic/kibana-operations +packages/kbn-storybook @elastic/kibana-operations +packages/kbn-telemetry-tools @elastic/kibana-core +packages/kbn-test @elastic/kibana-operations @elastic/appex-qa +packages/kbn-test-eui-helpers @elastic/kibana-visualizations +packages/kbn-test-jest-helpers @elastic/kibana-operations @elastic/appex-qa +packages/kbn-test-subj-selector @elastic/kibana-operations @elastic/appex-qa +packages/kbn-timelion-grammar @elastic/kibana-visualizations +packages/kbn-timerange @elastic/obs-ux-logs-team +packages/kbn-tinymath @elastic/kibana-visualizations +packages/kbn-tooling-log @elastic/kibana-operations +packages/kbn-transpose-utils @elastic/kibana-visualizations +packages/kbn-triggers-actions-ui-types @elastic/response-ops +packages/kbn-try-in-console @elastic/search-kibana +packages/kbn-ts-projects @elastic/kibana-operations +packages/kbn-ts-type-check-cli @elastic/kibana-operations +packages/kbn-typed-react-router-config @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team +packages/kbn-ui-actions-browser @elastic/appex-sharedux +packages/kbn-ui-shared-deps-npm @elastic/kibana-operations +packages/kbn-ui-shared-deps-src @elastic/kibana-operations +packages/kbn-ui-theme @elastic/kibana-operations +packages/kbn-unified-data-table @elastic/kibana-data-discovery @elastic/security-threat-hunting-investigations +packages/kbn-unified-doc-viewer @elastic/kibana-data-discovery +packages/kbn-unified-field-list @elastic/kibana-data-discovery +packages/kbn-unsaved-changes-badge @elastic/kibana-data-discovery +packages/kbn-unsaved-changes-prompt @elastic/kibana-management +packages/kbn-use-tracked-promise @elastic/obs-ux-logs-team +packages/kbn-user-profile-components @elastic/kibana-security +packages/kbn-utility-types @elastic/kibana-core +packages/kbn-utility-types-jest @elastic/kibana-operations +packages/kbn-utils @elastic/kibana-operations +packages/kbn-validate-next-docs-cli @elastic/kibana-operations +packages/kbn-visualization-ui-components @elastic/kibana-visualizations +packages/kbn-visualization-utils @elastic/kibana-visualizations +packages/kbn-web-worker-stub @elastic/kibana-operations +packages/kbn-whereis-pkg-cli @elastic/kibana-operations +packages/kbn-xstate-utils @elastic/obs-ux-logs-team +packages/kbn-yarn-lock-validator @elastic/kibana-operations +packages/kbn-zod @elastic/kibana-core +packages/kbn-zod-helpers @elastic/security-detection-rule-management +packages/presentation/presentation_containers @elastic/kibana-presentation +packages/presentation/presentation_publishing @elastic/kibana-presentation +packages/react/kibana_context/common @elastic/appex-sharedux +packages/react/kibana_context/render @elastic/appex-sharedux +packages/react/kibana_context/root @elastic/appex-sharedux +packages/react/kibana_context/styled @elastic/appex-sharedux +packages/react/kibana_context/theme @elastic/appex-sharedux +packages/react/kibana_mount @elastic/appex-sharedux +packages/response-ops/feature_flag_service @elastic/response-ops +packages/response-ops/rule_params @elastic/response-ops +packages/serverless/project_switcher @elastic/appex-sharedux packages/serverless/settings/common @elastic/appex-sharedux @elastic/kibana-management -x-pack/plugins/serverless_observability @elastic/obs-ux-management-team packages/serverless/settings/observability_project @elastic/appex-sharedux @elastic/kibana-management @elastic/obs-ux-management-team -packages/serverless/project_switcher @elastic/appex-sharedux -x-pack/plugins/serverless_search @elastic/search-kibana packages/serverless/settings/search_project @elastic/search-kibana @elastic/kibana-management packages/serverless/settings/security_project @elastic/security-solution @elastic/kibana-management packages/serverless/storybook/config @elastic/appex-sharedux packages/serverless/types @elastic/appex-sharedux -test/plugin_functional/plugins/session_notifications @elastic/kibana-core -x-pack/plugins/session_view @elastic/kibana-cloud-security-posture -packages/kbn-set-map @elastic/kibana-operations -examples/share_examples @elastic/appex-sharedux -src/plugins/share @elastic/appex-sharedux -packages/kbn-shared-svg @elastic/obs-ux-infra_services-team packages/shared-ux/avatar/solution @elastic/appex-sharedux -packages/shared-ux/button/exit_full_screen @elastic/appex-sharedux packages/shared-ux/button_toolbar @elastic/appex-sharedux +packages/shared-ux/button/exit_full_screen @elastic/appex-sharedux packages/shared-ux/card/no_data/impl @elastic/appex-sharedux packages/shared-ux/card/no_data/mocks @elastic/appex-sharedux packages/shared-ux/card/no_data/types @elastic/appex-sharedux packages/shared-ux/chrome/navigation @elastic/appex-sharedux +packages/shared-ux/code_editor/impl @elastic/appex-sharedux +packages/shared-ux/code_editor/mocks @elastic/appex-sharedux packages/shared-ux/error_boundary @elastic/appex-sharedux packages/shared-ux/file/context @elastic/appex-sharedux +packages/shared-ux/file/file_picker/impl @elastic/appex-sharedux +packages/shared-ux/file/file_upload/impl @elastic/appex-sharedux packages/shared-ux/file/image/impl @elastic/appex-sharedux packages/shared-ux/file/image/mocks @elastic/appex-sharedux packages/shared-ux/file/mocks @elastic/appex-sharedux -packages/shared-ux/file/file_picker/impl @elastic/appex-sharedux packages/shared-ux/file/types @elastic/appex-sharedux -packages/shared-ux/file/file_upload/impl @elastic/appex-sharedux packages/shared-ux/file/util @elastic/appex-sharedux packages/shared-ux/link/redirect_app/impl @elastic/appex-sharedux packages/shared-ux/link/redirect_app/mocks @elastic/appex-sharedux @@ -867,6 +586,7 @@ packages/shared-ux/link/redirect_app/types @elastic/appex-sharedux packages/shared-ux/markdown/impl @elastic/appex-sharedux packages/shared-ux/markdown/mocks @elastic/appex-sharedux packages/shared-ux/markdown/types @elastic/appex-sharedux +packages/shared-ux/modal/tabbed @elastic/appex-sharedux packages/shared-ux/page/analytics_no_data/impl @elastic/appex-sharedux packages/shared-ux/page/analytics_no_data/mocks @elastic/appex-sharedux packages/shared-ux/page/analytics_no_data/types @elastic/appex-sharedux @@ -876,10 +596,10 @@ packages/shared-ux/page/kibana_no_data/types @elastic/appex-sharedux packages/shared-ux/page/kibana_template/impl @elastic/appex-sharedux packages/shared-ux/page/kibana_template/mocks @elastic/appex-sharedux packages/shared-ux/page/kibana_template/types @elastic/appex-sharedux -packages/shared-ux/page/no_data/impl @elastic/appex-sharedux packages/shared-ux/page/no_data_config/impl @elastic/appex-sharedux packages/shared-ux/page/no_data_config/mocks @elastic/appex-sharedux packages/shared-ux/page/no_data_config/types @elastic/appex-sharedux +packages/shared-ux/page/no_data/impl @elastic/appex-sharedux packages/shared-ux/page/no_data/mocks @elastic/appex-sharedux packages/shared-ux/page/no_data/types @elastic/appex-sharedux packages/shared-ux/page/solution_nav @elastic/appex-sharedux @@ -892,109 +612,89 @@ packages/shared-ux/router/mocks @elastic/appex-sharedux packages/shared-ux/router/types @elastic/appex-sharedux packages/shared-ux/storybook/config @elastic/appex-sharedux packages/shared-ux/storybook/mock @elastic/appex-sharedux -packages/shared-ux/modal/tabbed @elastic/appex-sharedux packages/shared-ux/table_persist @elastic/appex-sharedux -packages/kbn-shared-ux-utility @elastic/appex-sharedux -x-pack/plugins/observability_solution/slo @elastic/obs-ux-management-team -x-pack/packages/kbn-slo-schema @elastic/obs-ux-management-team -x-pack/plugins/snapshot_restore @elastic/kibana-management -packages/kbn-some-dev-log @elastic/kibana-operations -packages/kbn-sort-package-json @elastic/kibana-operations -packages/kbn-sort-predicates @elastic/kibana-visualizations -x-pack/plugins/spaces @elastic/kibana-security -x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin @elastic/kibana-security -packages/kbn-sse-utils @elastic/obs-knowledge-team -packages/kbn-sse-utils-client @elastic/obs-knowledge-team -packages/kbn-sse-utils-server @elastic/obs-knowledge-team -x-pack/plugins/stack_alerts @elastic/response-ops -x-pack/plugins/stack_connectors @elastic/response-ops -x-pack/test/usage_collection/plugins/stack_management_usage_test @elastic/kibana-management -examples/state_containers_examples @elastic/appex-sharedux -test/server_integration/plugins/status_plugin_a @elastic/kibana-core -test/server_integration/plugins/status_plugin_b @elastic/kibana-core -packages/kbn-std @elastic/kibana-core -packages/kbn-stdio-dev-helpers @elastic/kibana-operations -packages/kbn-storybook @elastic/kibana-operations -x-pack/plugins/observability_solution/synthetics/e2e @elastic/obs-ux-management-team -x-pack/plugins/observability_solution/synthetics @elastic/obs-ux-management-team -x-pack/packages/kbn-synthetics-private-location @elastic/obs-ux-management-team -x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture @elastic/response-ops -x-pack/test/plugin_api_perf/plugins/task_manager_performance @elastic/response-ops -x-pack/plugins/task_manager @elastic/response-ops +src/core @elastic/kibana-core +src/plugins/advanced_settings @elastic/appex-sharedux @elastic/kibana-management +src/plugins/ai_assistant_management/selection @elastic/obs-knowledge-team +src/plugins/bfetch @elastic/appex-sharedux +src/plugins/chart_expressions/common @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_gauge @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_heatmap @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_legacy_metric @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_metric @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_partition_vis @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_tagcloud @elastic/kibana-visualizations +src/plugins/chart_expressions/expression_xy @elastic/kibana-visualizations +src/plugins/charts @elastic/kibana-visualizations +src/plugins/console @elastic/kibana-management +src/plugins/content_management @elastic/appex-sharedux +src/plugins/controls @elastic/kibana-presentation +src/plugins/custom_integrations @elastic/fleet +src/plugins/dashboard @elastic/kibana-presentation +src/plugins/data @elastic/kibana-visualizations @elastic/kibana-data-discovery +src/plugins/data_view_editor @elastic/kibana-data-discovery +src/plugins/data_view_field_editor @elastic/kibana-data-discovery +src/plugins/data_view_management @elastic/kibana-data-discovery +src/plugins/data_views @elastic/kibana-data-discovery +src/plugins/dev_tools @elastic/kibana-management +src/plugins/discover @elastic/kibana-data-discovery +src/plugins/discover_shared @elastic/kibana-data-discovery @elastic/obs-ux-logs-team +src/plugins/embeddable @elastic/kibana-presentation +src/plugins/es_ui_shared @elastic/kibana-management +src/plugins/esql @elastic/kibana-esql +src/plugins/esql_datagrid @elastic/kibana-esql +src/plugins/event_annotation @elastic/kibana-visualizations +src/plugins/event_annotation_listing @elastic/kibana-visualizations +src/plugins/expression_error @elastic/kibana-presentation +src/plugins/expression_image @elastic/kibana-presentation +src/plugins/expression_metric @elastic/kibana-presentation +src/plugins/expression_repeat_image @elastic/kibana-presentation +src/plugins/expression_reveal_image @elastic/kibana-presentation +src/plugins/expression_shape @elastic/kibana-presentation +src/plugins/expressions @elastic/kibana-visualizations +src/plugins/field_formats @elastic/kibana-data-discovery +src/plugins/files @elastic/appex-sharedux +src/plugins/files_management @elastic/appex-sharedux +src/plugins/ftr_apis @elastic/kibana-core +src/plugins/guided_onboarding @elastic/appex-sharedux +src/plugins/home @elastic/kibana-core +src/plugins/image_embeddable @elastic/appex-sharedux +src/plugins/input_control_vis @elastic/kibana-presentation +src/plugins/inspector @elastic/kibana-presentation +src/plugins/interactive_setup @elastic/kibana-security +src/plugins/kibana_overview @elastic/appex-sharedux +src/plugins/kibana_react @elastic/appex-sharedux +src/plugins/kibana_usage_collection @elastic/kibana-core +src/plugins/kibana_utils @elastic/appex-sharedux +src/plugins/links @elastic/kibana-presentation +src/plugins/management @elastic/kibana-management +src/plugins/maps_ems @elastic/kibana-presentation +src/plugins/navigation @elastic/appex-sharedux +src/plugins/newsfeed @elastic/kibana-core +src/plugins/no_data_page @elastic/appex-sharedux +src/plugins/presentation_panel @elastic/kibana-presentation +src/plugins/presentation_util @elastic/kibana-presentation +src/plugins/saved_objects @elastic/appex-sharedux +src/plugins/saved_objects_finder @elastic/kibana-data-discovery +src/plugins/saved_objects_management @elastic/kibana-core +src/plugins/saved_objects_tagging_oss @elastic/appex-sharedux +src/plugins/saved_search @elastic/kibana-data-discovery +src/plugins/screenshot_mode @elastic/appex-sharedux +src/plugins/share @elastic/appex-sharedux +src/plugins/telemetry @elastic/kibana-core src/plugins/telemetry_collection_manager @elastic/kibana-core -x-pack/plugins/telemetry_collection_xpack @elastic/kibana-core src/plugins/telemetry_management_section @elastic/kibana-core -src/plugins/telemetry @elastic/kibana-core -test/plugin_functional/plugins/telemetry @elastic/kibana-core -packages/kbn-telemetry-tools @elastic/kibana-core -packages/kbn-test @elastic/kibana-operations @elastic/appex-qa -packages/kbn-test-eui-helpers @elastic/kibana-visualizations -x-pack/test/licensing_plugin/plugins/test_feature_usage @elastic/kibana-security -packages/kbn-test-jest-helpers @elastic/kibana-operations @elastic/appex-qa -packages/kbn-test-subj-selector @elastic/kibana-operations @elastic/appex-qa -x-pack/test_serverless -test -x-pack/test -x-pack/performance @elastic/appex-qa -x-pack/examples/testing_embedded_lens @elastic/kibana-visualizations -x-pack/examples/third_party_lens_navigation_prompt @elastic/kibana-visualizations -x-pack/examples/third_party_vis_lens_example @elastic/kibana-visualizations -x-pack/plugins/threat_intelligence @elastic/security-threat-hunting-investigations -x-pack/plugins/timelines @elastic/security-threat-hunting-investigations -packages/kbn-timelion-grammar @elastic/kibana-visualizations -packages/kbn-timerange @elastic/obs-ux-logs-team -packages/kbn-tinymath @elastic/kibana-visualizations -packages/kbn-tooling-log @elastic/kibana-operations -x-pack/plugins/transform @elastic/ml-ui -x-pack/plugins/translations @elastic/kibana-localization -packages/kbn-transpose-utils @elastic/kibana-visualizations -x-pack/examples/triggers_actions_ui_example @elastic/response-ops -x-pack/plugins/triggers_actions_ui @elastic/response-ops -packages/kbn-triggers-actions-ui-types @elastic/response-ops -packages/kbn-try-in-console @elastic/search-kibana -packages/kbn-ts-projects @elastic/kibana-operations -packages/kbn-ts-type-check-cli @elastic/kibana-operations -packages/kbn-typed-react-router-config @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team -packages/kbn-ui-actions-browser @elastic/appex-sharedux -x-pack/examples/ui_actions_enhanced_examples @elastic/appex-sharedux -src/plugins/ui_actions_enhanced @elastic/appex-sharedux -examples/ui_action_examples @elastic/appex-sharedux -examples/ui_actions_explorer @elastic/appex-sharedux src/plugins/ui_actions @elastic/appex-sharedux -test/plugin_functional/plugins/ui_settings_plugin @elastic/kibana-core -packages/kbn-ui-shared-deps-npm @elastic/kibana-operations -packages/kbn-ui-shared-deps-src @elastic/kibana-operations -packages/kbn-ui-theme @elastic/kibana-operations -packages/kbn-unified-data-table @elastic/kibana-data-discovery @elastic/security-threat-hunting-investigations -packages/kbn-unified-doc-viewer @elastic/kibana-data-discovery -examples/unified_doc_viewer @elastic/kibana-core +src/plugins/ui_actions_enhanced @elastic/appex-sharedux src/plugins/unified_doc_viewer @elastic/kibana-data-discovery -packages/kbn-unified-field-list @elastic/kibana-data-discovery -examples/unified_field_list_examples @elastic/kibana-data-discovery src/plugins/unified_histogram @elastic/kibana-data-discovery src/plugins/unified_search @elastic/kibana-visualizations -packages/kbn-unsaved-changes-badge @elastic/kibana-data-discovery -packages/kbn-unsaved-changes-prompt @elastic/kibana-management -x-pack/plugins/upgrade_assistant @elastic/kibana-management -x-pack/plugins/observability_solution/uptime @elastic/obs-ux-management-team -x-pack/plugins/drilldowns/url_drilldown @elastic/appex-sharedux src/plugins/url_forwarding @elastic/kibana-visualizations src/plugins/usage_collection @elastic/kibana-core -test/plugin_functional/plugins/usage_collection @elastic/kibana-core -packages/kbn-use-tracked-promise @elastic/obs-ux-logs-team -packages/kbn-user-profile-components @elastic/kibana-security -examples/user_profile_examples @elastic/kibana-security -x-pack/test/security_api_integration/plugins/user_profiles_consumer @elastic/kibana-security -packages/kbn-utility-types @elastic/kibana-core -packages/kbn-utility-types-jest @elastic/kibana-operations -packages/kbn-utils @elastic/kibana-operations -x-pack/plugins/observability_solution/ux @elastic/obs-ux-infra_services-team -examples/v8_profiler_examples @elastic/response-ops -packages/kbn-validate-next-docs-cli @elastic/kibana-operations src/plugins/vis_default_editor @elastic/kibana-visualizations +src/plugins/vis_type_markdown @elastic/kibana-presentation src/plugins/vis_types/gauge @elastic/kibana-visualizations src/plugins/vis_types/heatmap @elastic/kibana-visualizations -src/plugins/vis_type_markdown @elastic/kibana-presentation src/plugins/vis_types/metric @elastic/kibana-visualizations src/plugins/vis_types/pie @elastic/kibana-visualizations src/plugins/vis_types/table @elastic/kibana-visualizations @@ -1004,16 +704,316 @@ src/plugins/vis_types/timeseries @elastic/kibana-visualizations src/plugins/vis_types/vega @elastic/kibana-visualizations src/plugins/vis_types/vislib @elastic/kibana-visualizations src/plugins/vis_types/xy @elastic/kibana-visualizations -packages/kbn-visualization-ui-components @elastic/kibana-visualizations -packages/kbn-visualization-utils @elastic/kibana-visualizations src/plugins/visualizations @elastic/kibana-visualizations +test +test/analytics/plugins/analytics_ftr_helpers @elastic/kibana-core +test/analytics/plugins/analytics_plugin_a @elastic/kibana-core +test/common/plugins/newsfeed @elastic/kibana-core +test/common/plugins/otel_metrics @elastic/obs-ux-infra_services-team +test/health_gateway/plugins/status @elastic/kibana-core +test/interactive_setup_api_integration/plugins/test_endpoints @elastic/kibana-security +test/interpreter_functional/plugins/kbn_tp_run_pipeline @elastic/kibana-core +test/node_roles_functional/plugins/core_plugin_initializer_context @elastic/kibana-core +test/plugin_functional/plugins/app_link_test @elastic/kibana-core +test/plugin_functional/plugins/core_app_status @elastic/kibana-core +test/plugin_functional/plugins/core_dynamic_resolving_a @elastic/kibana-core +test/plugin_functional/plugins/core_dynamic_resolving_b @elastic/kibana-core +test/plugin_functional/plugins/core_history_block @elastic/kibana-core +test/plugin_functional/plugins/core_http @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_a @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_appleave @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_b @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_chromeless @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_deep_links @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_deprecations @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_execution_context @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_helpmenu @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_route_timeouts @elastic/kibana-core +test/plugin_functional/plugins/core_plugin_static_assets @elastic/kibana-core +test/plugin_functional/plugins/core_provider_plugin @elastic/kibana-core +test/plugin_functional/plugins/data_search @elastic/kibana-data-discovery +test/plugin_functional/plugins/elasticsearch_client_plugin @elastic/kibana-core +test/plugin_functional/plugins/eui_provider_dev_warning @elastic/appex-sharedux +test/plugin_functional/plugins/hardening @elastic/kibana-security +test/plugin_functional/plugins/index_patterns @elastic/kibana-data-discovery +test/plugin_functional/plugins/kbn_sample_panel_action @elastic/appex-sharedux +test/plugin_functional/plugins/kbn_top_nav @elastic/kibana-core +test/plugin_functional/plugins/kbn_tp_custom_visualizations @elastic/kibana-visualizations +test/plugin_functional/plugins/management_test_plugin @elastic/kibana-management +test/plugin_functional/plugins/rendering_plugin @elastic/kibana-core +test/plugin_functional/plugins/saved_object_export_transforms @elastic/kibana-core +test/plugin_functional/plugins/saved_object_import_warnings @elastic/kibana-core +test/plugin_functional/plugins/saved_objects_hidden_from_http_apis_type @elastic/kibana-core +test/plugin_functional/plugins/saved_objects_hidden_type @elastic/kibana-core +test/plugin_functional/plugins/session_notifications @elastic/kibana-core +test/plugin_functional/plugins/telemetry @elastic/kibana-core +test/plugin_functional/plugins/ui_settings_plugin @elastic/kibana-core +test/plugin_functional/plugins/usage_collection @elastic/kibana-core +test/server_integration/plugins/status_plugin_a @elastic/kibana-core +test/server_integration/plugins/status_plugin_b @elastic/kibana-core +x-pack/examples/alerting_example @elastic/response-ops +x-pack/examples/embedded_lens_example @elastic/kibana-visualizations +x-pack/examples/exploratory_view_example @elastic/obs-ux-infra_services-team +x-pack/examples/gen_ai_streaming_response_example @elastic/response-ops +x-pack/examples/lens_config_builder_example @elastic/kibana-visualizations +x-pack/examples/lens_embeddable_inline_editing_example @elastic/kibana-visualizations +x-pack/examples/screenshotting_example @elastic/appex-sharedux +x-pack/examples/testing_embedded_lens @elastic/kibana-visualizations +x-pack/examples/third_party_lens_navigation_prompt @elastic/kibana-visualizations +x-pack/examples/third_party_maps_source_example @elastic/kibana-presentation +x-pack/examples/third_party_vis_lens_example @elastic/kibana-visualizations +x-pack/examples/triggers_actions_ui_example @elastic/response-ops +x-pack/examples/ui_actions_enhanced_examples @elastic/appex-sharedux +x-pack/packages/ai-infra/inference-common @elastic/appex-ai-infra +x-pack/packages/ai-infra/product-doc-artifact-builder @elastic/appex-ai-infra +x-pack/packages/index-management/index_management_shared_types @elastic/kibana-management +x-pack/packages/kbn-ai-assistant @elastic/search-kibana +x-pack/packages/kbn-ai-assistant-common @elastic/search-kibana +x-pack/packages/kbn-alerting-comparators @elastic/response-ops +x-pack/packages/kbn-alerting-state-types @elastic/response-ops +x-pack/packages/kbn-cloud-security-posture/common @elastic/kibana-cloud-security-posture +x-pack/packages/kbn-cloud-security-posture/graph @elastic/kibana-cloud-security-posture +x-pack/packages/kbn-cloud-security-posture/public @elastic/kibana-cloud-security-posture +x-pack/packages/kbn-data-forge @elastic/obs-ux-management-team +x-pack/packages/kbn-elastic-assistant @elastic/security-generative-ai +x-pack/packages/kbn-elastic-assistant-common @elastic/security-generative-ai +x-pack/packages/kbn-entities-schema @elastic/obs-entities +x-pack/packages/kbn-infra-forge @elastic/obs-ux-management-team +x-pack/packages/kbn-langchain @elastic/security-generative-ai +x-pack/packages/kbn-random-sampling @elastic/kibana-visualizations +x-pack/packages/kbn-slo-schema @elastic/obs-ux-management-team +x-pack/packages/kbn-synthetics-private-location @elastic/obs-ux-management-team +x-pack/packages/maps/vector_tile_utils @elastic/kibana-presentation +x-pack/packages/ml/agg_utils @elastic/ml-ui +x-pack/packages/ml/aiops_change_point_detection @elastic/ml-ui +x-pack/packages/ml/aiops_common @elastic/ml-ui +x-pack/packages/ml/aiops_components @elastic/ml-ui +x-pack/packages/ml/aiops_log_pattern_analysis @elastic/ml-ui +x-pack/packages/ml/aiops_log_rate_analysis @elastic/ml-ui +x-pack/packages/ml/aiops_test_utils @elastic/ml-ui +x-pack/packages/ml/anomaly_utils @elastic/ml-ui +x-pack/packages/ml/cancellable_search @elastic/ml-ui +x-pack/packages/ml/category_validator @elastic/ml-ui +x-pack/packages/ml/chi2test @elastic/ml-ui +x-pack/packages/ml/creation_wizard_utils @elastic/ml-ui +x-pack/packages/ml/data_frame_analytics_utils @elastic/ml-ui +x-pack/packages/ml/data_grid @elastic/ml-ui +x-pack/packages/ml/data_view_utils @elastic/ml-ui +x-pack/packages/ml/date_picker @elastic/ml-ui +x-pack/packages/ml/date_utils @elastic/ml-ui +x-pack/packages/ml/error_utils @elastic/ml-ui +x-pack/packages/ml/field_stats_flyout @elastic/ml-ui +x-pack/packages/ml/in_memory_table @elastic/ml-ui +x-pack/packages/ml/inference_integration_flyout @elastic/ml-ui +x-pack/packages/ml/is_defined @elastic/ml-ui +x-pack/packages/ml/is_populated_object @elastic/ml-ui +x-pack/packages/ml/json_schemas @elastic/ml-ui +x-pack/packages/ml/kibana_theme @elastic/ml-ui +x-pack/packages/ml/local_storage @elastic/ml-ui +x-pack/packages/ml/nested_property @elastic/ml-ui +x-pack/packages/ml/number_utils @elastic/ml-ui +x-pack/packages/ml/parse_interval @elastic/ml-ui +x-pack/packages/ml/query_utils @elastic/ml-ui +x-pack/packages/ml/random_sampler_utils @elastic/ml-ui +x-pack/packages/ml/response_stream @elastic/ml-ui +x-pack/packages/ml/route_utils @elastic/ml-ui +x-pack/packages/ml/runtime_field_utils @elastic/ml-ui +x-pack/packages/ml/string_hash @elastic/ml-ui +x-pack/packages/ml/time_buckets @elastic/ml-ui +x-pack/packages/ml/trained_models_utils @elastic/ml-ui +x-pack/packages/ml/ui_actions @elastic/ml-ui +x-pack/packages/ml/url_state @elastic/ml-ui +x-pack/packages/ml/validators @elastic/ml-ui +x-pack/packages/observability/alert_details @elastic/obs-ux-management-team +x-pack/packages/observability/alerting_rule_utils @elastic/obs-ux-management-team +x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team +x-pack/packages/observability/get_padded_alert_time_range_util @elastic/obs-ux-management-team +x-pack/packages/observability/logs_overview @elastic/obs-ux-logs-team +x-pack/packages/observability/observability_utils @elastic/observability-ui +x-pack/packages/observability/synthetics_test_data @elastic/obs-ux-management-team +x-pack/packages/rollup @elastic/kibana-management +x-pack/packages/search/shared_ui @elastic/search-kibana +x-pack/packages/security-solution/data_table @elastic/security-threat-hunting-investigations +x-pack/packages/security-solution/distribution_bar @elastic/kibana-cloud-security-posture +x-pack/packages/security-solution/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/features @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/navigation @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/side_nav @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/storybook/config @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/upselling @elastic/security-threat-hunting-explore +x-pack/packages/security/api_key_management @elastic/kibana-security +x-pack/packages/security/authorization_core @elastic/kibana-security +x-pack/packages/security/authorization_core_common @elastic/kibana-security +x-pack/packages/security/form_components @elastic/kibana-security +x-pack/packages/security/plugin_types_common @elastic/kibana-security +x-pack/packages/security/plugin_types_public @elastic/kibana-security +x-pack/packages/security/plugin_types_server @elastic/kibana-security +x-pack/packages/security/role_management_model @elastic/kibana-security +x-pack/packages/security/ui_components @elastic/kibana-security +x-pack/performance @elastic/appex-qa +x-pack/plugins/actions @elastic/response-ops +x-pack/plugins/aiops @elastic/ml-ui +x-pack/plugins/alerting @elastic/response-ops +x-pack/plugins/banners @elastic/appex-sharedux +x-pack/plugins/canvas @elastic/kibana-presentation +x-pack/plugins/cases @elastic/response-ops +x-pack/plugins/cloud @elastic/kibana-core +x-pack/plugins/cloud_defend @elastic/kibana-cloud-security-posture +x-pack/plugins/cloud_integrations/cloud_chat @elastic/kibana-core +x-pack/plugins/cloud_integrations/cloud_data_migration @elastic/kibana-management +x-pack/plugins/cloud_integrations/cloud_experiments @elastic/kibana-core +x-pack/plugins/cloud_integrations/cloud_full_story @elastic/kibana-core +x-pack/plugins/cloud_integrations/cloud_links @elastic/kibana-core +x-pack/plugins/cloud_security_posture @elastic/kibana-cloud-security-posture +x-pack/plugins/cross_cluster_replication @elastic/kibana-management +x-pack/plugins/custom_branding @elastic/appex-sharedux +x-pack/plugins/dashboard_enhanced @elastic/kibana-presentation +x-pack/plugins/data_quality @elastic/obs-ux-logs-team +x-pack/plugins/data_usage @elastic/obs-ai-assistant @elastic/security-solution +x-pack/plugins/data_visualizer @elastic/ml-ui +x-pack/plugins/discover_enhanced @elastic/kibana-data-discovery +x-pack/plugins/drilldowns/url_drilldown @elastic/appex-sharedux +x-pack/plugins/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore +x-pack/plugins/elastic_assistant @elastic/security-generative-ai +x-pack/plugins/embeddable_enhanced @elastic/kibana-presentation +x-pack/plugins/encrypted_saved_objects @elastic/kibana-security +x-pack/plugins/enterprise_search @elastic/search-kibana +x-pack/plugins/entity_manager @elastic/obs-entities +x-pack/plugins/event_log @elastic/response-ops +x-pack/plugins/features @elastic/kibana-core +x-pack/plugins/fields_metadata @elastic/obs-ux-logs-team +x-pack/plugins/file_upload @elastic/kibana-presentation @elastic/ml-ui +x-pack/plugins/fleet @elastic/fleet +x-pack/plugins/global_search @elastic/appex-sharedux +x-pack/plugins/global_search_bar @elastic/appex-sharedux +x-pack/plugins/global_search_providers @elastic/appex-sharedux +x-pack/plugins/graph @elastic/kibana-visualizations +x-pack/plugins/grokdebugger @elastic/kibana-management +x-pack/plugins/index_lifecycle_management @elastic/kibana-management +x-pack/plugins/index_management @elastic/kibana-management +x-pack/plugins/inference @elastic/appex-ai-infra +x-pack/plugins/ingest_pipelines @elastic/kibana-management +x-pack/plugins/integration_assistant @elastic/security-scalability +x-pack/plugins/kubernetes_security @elastic/kibana-cloud-security-posture +x-pack/plugins/lens @elastic/kibana-visualizations +x-pack/plugins/license_api_guard @elastic/kibana-management +x-pack/plugins/license_management @elastic/kibana-management +x-pack/plugins/licensing @elastic/kibana-core +x-pack/plugins/lists @elastic/security-detection-engine +x-pack/plugins/logstash @elastic/logstash +x-pack/plugins/maps @elastic/kibana-presentation +x-pack/plugins/ml @elastic/ml-ui +x-pack/plugins/monitoring @elastic/stack-monitoring +x-pack/plugins/monitoring_collection @elastic/stack-monitoring +x-pack/plugins/notifications @elastic/appex-sharedux +x-pack/plugins/observability_solution/apm @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/apm_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/apm/ftr_e2e @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/dataset_quality @elastic/obs-ux-logs-team +x-pack/plugins/observability_solution/entities_data_access @elastic/obs-entities +x-pack/plugins/observability_solution/exploratory_view @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/infra @elastic/obs-ux-logs-team @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/inventory @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/inventory/e2e @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/investigate @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/investigate_app @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/logs_data_access @elastic/obs-knowledge-team @elastic/obs-ux-logs-team +x-pack/plugins/observability_solution/logs_explorer @elastic/obs-ux-logs-team +x-pack/plugins/observability_solution/logs_shared @elastic/obs-ux-logs-team +x-pack/plugins/observability_solution/metrics_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/observability @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/observability_ai_assistant @elastic/obs-ai-assistant +x-pack/plugins/observability_solution/observability_ai_assistant_app @elastic/obs-ai-assistant +x-pack/plugins/observability_solution/observability_ai_assistant_management @elastic/obs-ai-assistant +x-pack/plugins/observability_solution/observability_logs_explorer @elastic/obs-ux-logs-team +x-pack/plugins/observability_solution/observability_onboarding @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team +x-pack/plugins/observability_solution/observability_onboarding/e2e @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team +x-pack/plugins/observability_solution/observability_shared @elastic/observability-ui +x-pack/plugins/observability_solution/profiling @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/profiling_data_access @elastic/obs-ux-infra_services-team +x-pack/plugins/observability_solution/slo @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/synthetics @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/synthetics/e2e @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/uptime @elastic/obs-ux-management-team +x-pack/plugins/observability_solution/ux @elastic/obs-ux-infra_services-team +x-pack/plugins/osquery @elastic/security-defend-workflows +x-pack/plugins/painless_lab @elastic/kibana-management +x-pack/plugins/remote_clusters @elastic/kibana-management +x-pack/plugins/reporting @elastic/appex-sharedux +x-pack/plugins/rollup @elastic/kibana-management +x-pack/plugins/rule_registry @elastic/response-ops @elastic/obs-ux-management-team +x-pack/plugins/runtime_fields @elastic/kibana-management +x-pack/plugins/saved_objects_tagging @elastic/appex-sharedux +x-pack/plugins/screenshotting @elastic/kibana-reporting-services +x-pack/plugins/search_assistant @elastic/search-kibana +x-pack/plugins/search_connectors @elastic/search-kibana +x-pack/plugins/search_homepage @elastic/search-kibana +x-pack/plugins/search_indices @elastic/search-kibana +x-pack/plugins/search_inference_endpoints @elastic/search-kibana +x-pack/plugins/search_notebooks @elastic/search-kibana +x-pack/plugins/search_playground @elastic/search-kibana +x-pack/plugins/searchprofiler @elastic/kibana-management +x-pack/plugins/security @elastic/kibana-security +x-pack/plugins/security_solution @elastic/security-solution +x-pack/plugins/security_solution_ess @elastic/security-solution +x-pack/plugins/security_solution_serverless @elastic/security-solution +x-pack/plugins/serverless @elastic/appex-sharedux +x-pack/plugins/serverless_observability @elastic/obs-ux-management-team +x-pack/plugins/serverless_search @elastic/search-kibana +x-pack/plugins/session_view @elastic/kibana-cloud-security-posture +x-pack/plugins/snapshot_restore @elastic/kibana-management +x-pack/plugins/spaces @elastic/kibana-security +x-pack/plugins/stack_alerts @elastic/response-ops +x-pack/plugins/stack_connectors @elastic/response-ops +x-pack/plugins/task_manager @elastic/response-ops +x-pack/plugins/telemetry_collection_xpack @elastic/kibana-core +x-pack/plugins/threat_intelligence @elastic/security-threat-hunting-investigations +x-pack/plugins/timelines @elastic/security-threat-hunting-investigations +x-pack/plugins/transform @elastic/ml-ui +x-pack/plugins/translations @elastic/kibana-localization +x-pack/plugins/triggers_actions_ui @elastic/response-ops +x-pack/plugins/upgrade_assistant @elastic/kibana-management x-pack/plugins/watcher @elastic/kibana-management -packages/kbn-web-worker-stub @elastic/kibana-operations -packages/kbn-whereis-pkg-cli @elastic/kibana-operations -packages/kbn-xstate-utils @elastic/obs-ux-logs-team -packages/kbn-yarn-lock-validator @elastic/kibana-operations -packages/kbn-zod @elastic/kibana-core -packages/kbn-zod-helpers @elastic/security-detection-rule-management +x-pack/test +x-pack/test_serverless +x-pack/test/alerting_api_integration/common/plugins/aad @elastic/response-ops +x-pack/test/alerting_api_integration/common/plugins/actions_simulators @elastic/response-ops +x-pack/test/alerting_api_integration/common/plugins/alerts @elastic/response-ops +x-pack/test/alerting_api_integration/common/plugins/alerts_restricted @elastic/response-ops +x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture @elastic/response-ops +x-pack/test/alerting_api_integration/packages/helpers @elastic/response-ops +x-pack/test/api_integration/apis/entity_manager/fixture_plugin @elastic/obs-entities +x-pack/test/cases_api_integration/common/plugins/cases @elastic/response-ops +x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops +x-pack/test/cases_api_integration/common/plugins/security_solution @elastic/response-ops +x-pack/test/cloud_integration/plugins/saml_provider @elastic/kibana-core +x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin @elastic/kibana-security +x-pack/test/functional_cors/plugins/kibana_cors_test @elastic/kibana-security +x-pack/test/functional_embedded/plugins/iframe_embedded @elastic/kibana-core +x-pack/test/functional_execution_context/plugins/alerts @elastic/kibana-core +x-pack/test/functional_with_es_ssl/plugins/alerts @elastic/response-ops +x-pack/test/functional_with_es_ssl/plugins/cases @elastic/response-ops +x-pack/test/licensing_plugin/plugins/test_feature_usage @elastic/kibana-security +x-pack/test/plugin_api_integration/plugins/elasticsearch_client @elastic/kibana-core +x-pack/test/plugin_api_integration/plugins/event_log @elastic/response-ops +x-pack/test/plugin_api_integration/plugins/feature_usage_test @elastic/kibana-security +x-pack/test/plugin_api_integration/plugins/sample_task_plugin @elastic/response-ops +x-pack/test/plugin_api_perf/plugins/task_manager_performance @elastic/response-ops +x-pack/test/plugin_functional/plugins/global_search_test @elastic/kibana-core +x-pack/test/plugin_functional/plugins/resolver_test @elastic/security-solution +x-pack/test/saved_object_api_integration/common/plugins/saved_object_test_plugin @elastic/kibana-security +x-pack/test/security_api_integration/packages/helpers @elastic/kibana-security +x-pack/test/security_api_integration/plugins/audit_log @elastic/kibana-security +x-pack/test/security_api_integration/plugins/features_provider @elastic/kibana-security +x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security +x-pack/test/security_api_integration/plugins/saml_provider @elastic/kibana-security +x-pack/test/security_api_integration/plugins/user_profiles_consumer @elastic/kibana-security +x-pack/test/security_functional/plugins/test_endpoints @elastic/kibana-security +x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin @elastic/kibana-security +x-pack/test/task_manager_claimer_update_by_query/plugins/sample_task_plugin_mget @elastic/response-ops +x-pack/test/ui_capabilities/common/plugins/foo_plugin @elastic/kibana-security +x-pack/test/usage_collection/plugins/application_usage_test @elastic/kibana-core +x-pack/test/usage_collection/plugins/stack_management_usage_test @elastic/kibana-management #### ## Everything below this line overrides the default assignments for each package. ## Items lower in the file have higher precedence: diff --git a/packages/kbn-generate/src/commands/codeowners_command.ts b/packages/kbn-generate/src/commands/codeowners_command.ts index a86b4250d6850..ea76bc3a659c3 100644 --- a/packages/kbn-generate/src/commands/codeowners_command.ts +++ b/packages/kbn-generate/src/commands/codeowners_command.ts @@ -62,6 +62,11 @@ export const CodeownersCommand: GenerateCommand = { content = content.slice(0, ultStart); } + // sort genarated entries by directory name + // this improves readability and makes sure that ownership for nested + // test plugins is not overriden by the parent package's entry + pkgs.sort((a, b) => a.directory.localeCompare(b.directory)); + const newCodeowners = `${GENERATED_START}${pkgs .map( (pkg) => From e803f5c794348fe2330cfc77df4003dc55ce7929 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:41:43 +1100 Subject: [PATCH 08/53] Authorized route migration for routes owned by security-threat-hunting (#198386) ### Authz API migration for authorized routes This PR migrates `access:` tags used in route definitions to 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:** Access control tags were defined in the `options` object of the route: ```ts router.get({ path: '/api/path', options: { tags: ['access:', 'access:'], }, ... }, handler); ``` ### **After migration:** Tags have been replaced with the more robust `security.authz.requiredPrivileges` field under `security`: ```ts router.get({ path: '/api/path', security: { authz: { requiredPrivileges: ['', ''], }, }, ... }, handler); ``` ### What to do next? 1. Review the changes in this PR. 2. You might need to update your tests to reflect the new security configuration: - If you have tests that rely on checking `access` tags. - If you have snapshot tests that include the route definition. - If you have FTR tests that rely on checking unauthorized error message. The error message changed to also include missing privileges. ## 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: Angela Chuang <6295984+angorayc@users.noreply.github.com> --- .../server/lib/siem_migrations/rules/api/create.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts index e2cf97dd094a9..f4c52e9b444b8 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts @@ -25,7 +25,11 @@ export const registerSiemRuleMigrationsCreateRoute = ( .post({ path: SIEM_RULE_MIGRATIONS_PATH, access: 'internal', - options: { tags: ['access:securitySolution'] }, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, }) .addVersion( { From c1e00a887173e8b86fd58fa0e7e768e4f0b1643c Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 6 Nov 2024 13:45:01 +0100 Subject: [PATCH 09/53] [Discover] Show the fetched Discover results even when histogram request fails on some shards (#198553) - Closes https://github.com/elastic/kibana/issues/198496 ## Summary This PR fixes an issue when the histogram request returns only a partial result (0 or greater than 0) by adding a warning icon next to the total hits counter and not blocking the whole page with "No results" message (when partial result with 0 hits from histogram). Screenshot 2024-10-31 at 15 45 17 ### Testing Execute the following and open `example*` data view in Discover. ``` PUT example1 PUT example1/_mapping { "properties": { "message": { "type": "text" }, "date": { "type": "date" } } } PUT example1/_doc/11 { "message": "11", "date": "2024-11-11T12:10:30Z" } PUT example1/_doc/12 { "message": "22", "date": "2024-11-12T12:10:30Z" } PUT example2 PUT example2/_mapping { "properties": { "message": { "type": "keyword" }, "date": { "type": "date" } } } PUT example2/_doc/21 { "message": "21", "date": "2024-12-01T12:10:30Z" } PUT example2/_doc/22 { "message": "22", "date": "2024-12-02T12:10:30Z" } ``` Then add `message` as a breakdown field. Notice that the histogram gets some partial results: Screenshot 2024-10-31 at 16 11 14 Now, add a filter for `_id: 11` and notice that the histogram request has no results (it partially failed on some shards) but Discover still renders the table: Screenshot 2024-10-31 at 16 11 31 ### Checklist - [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 --------- Co-authored-by: Davis McPhee --- .../hits_counter/hits_counter.test.tsx | 60 ++++++++++++++++- .../components/hits_counter/hits_counter.tsx | 65 +++++++++++++------ .../public/chart/histogram.test.tsx | 2 +- .../public/chart/histogram.tsx | 15 +++-- .../ccs_compatibility/_search_errors.ts | 2 +- test/functional/page_objects/discover_page.ts | 6 +- 6 files changed, 119 insertions(+), 31 deletions(-) diff --git a/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx b/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx index 18f9c8b629b3b..e34ad87d403e5 100644 --- a/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx +++ b/src/plugins/discover/public/components/hits_counter/hits_counter.test.tsx @@ -14,8 +14,20 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { EuiLoadingSpinner } from '@elastic/eui'; import { BehaviorSubject } from 'rxjs'; import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock'; -import { DataTotalHits$ } from '../../application/main/state_management/discover_data_state_container'; +import { + DataDocuments$, + DataTotalHits$, +} from '../../application/main/state_management/discover_data_state_container'; import { FetchStatus } from '../../application/types'; +import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__'; +import { buildDataTableRecord } from '@kbn/discover-utils'; + +function getDocuments$(count: number = 5) { + return new BehaviorSubject({ + fetchStatus: FetchStatus.COMPLETE, + result: esHitsMock.map((esHit) => buildDataTableRecord(esHit, dataViewMock)).slice(0, count), + }) as DataDocuments$; +} describe('hits counter', function () { it('expect to render the number of hits', function () { @@ -24,6 +36,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.COMPLETE, result: 1, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component1 = mountWithIntl( ); @@ -45,6 +58,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.COMPLETE, result: 1899, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component1 = mountWithIntl( ); @@ -64,6 +78,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.PARTIAL, result: 2, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component = mountWithIntl( ); @@ -76,6 +91,7 @@ describe('hits counter', function () { fetchStatus: FetchStatus.PARTIAL, result: 2, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component = mountWithIntl( ); @@ -89,9 +105,51 @@ describe('hits counter', function () { fetchStatus: FetchStatus.LOADING, result: undefined, }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(); const component = mountWithIntl( ); expect(component.isEmptyRender()).toBe(true); }); + + it('should render discoverQueryHitsPartial when status is error', () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + stateContainer.dataState.data$.totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.ERROR, + result: undefined, + }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(3); + const component = mountWithIntl( + + ); + expect(component.find('[data-test-subj="discoverQueryHitsPartial"]').length).toBe(1); + expect(findTestSubject(component, 'discoverQueryTotalHits').text()).toBe('≥3 resultsInfo'); + expect(component.text()).toBe('≥3 resultsInfo'); + + stateContainer.dataState.data$.totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.ERROR, + result: 200, + }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(2); + + const component2 = mountWithIntl( + + ); + expect(component2.find('[data-test-subj="discoverQueryHitsPartial"]').length).toBe(1); + expect(findTestSubject(component2, 'discoverQueryTotalHits').text()).toBe('≥200Info'); + expect(component2.text()).toBe(' (≥200Info)'); + + stateContainer.dataState.data$.totalHits$ = new BehaviorSubject({ + fetchStatus: FetchStatus.ERROR, + result: 0, + }) as DataTotalHits$; + stateContainer.dataState.data$.documents$ = getDocuments$(1); + + const component3 = mountWithIntl( + + ); + expect(component3.find('[data-test-subj="discoverQueryHitsPartial"]').length).toBe(1); + expect(findTestSubject(component3, 'discoverQueryTotalHits').text()).toBe('≥1Info'); + expect(component3.text()).toBe(' (≥1Info)'); + }); }); diff --git a/src/plugins/discover/public/components/hits_counter/hits_counter.tsx b/src/plugins/discover/public/components/hits_counter/hits_counter.tsx index fc89183ff864d..203d32ce97f58 100644 --- a/src/plugins/discover/public/components/hits_counter/hits_counter.tsx +++ b/src/plugins/discover/public/components/hits_counter/hits_counter.tsx @@ -8,7 +8,7 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingSpinner, EuiIconTip } from '@elastic/eui'; import { FormattedMessage, FormattedNumber } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; @@ -29,18 +29,33 @@ export interface HitsCounterProps { export const HitsCounter: React.FC = ({ mode, stateContainer }) => { const totalHits$ = stateContainer.dataState.data$.totalHits$; const totalHitsState = useDataState(totalHits$); - const hitsTotal = totalHitsState.result; + let hitsTotal = totalHitsState.result; const hitsStatus = totalHitsState.fetchStatus; + const documents$ = stateContainer.dataState.data$.documents$; + const documentsState = useDataState(documents$); + const documentsCount = documentsState.result?.length || 0; + if (!hitsTotal && hitsStatus === FetchStatus.LOADING) { return null; } + if ( + hitsStatus === FetchStatus.ERROR && + documentsState.fetchStatus === FetchStatus.COMPLETE && + documentsCount > (hitsTotal ?? 0) + ) { + // if histogram returned partial results and which are less than the fetched documents count => + // override hitsTotal with the fetched documents count + hitsTotal = documentsCount; + } + + const showGreaterOrEqualSign = + hitsStatus === FetchStatus.PARTIAL || hitsStatus === FetchStatus.ERROR; + const formattedHits = ( @@ -55,7 +70,7 @@ export const HitsCounter: React.FC = ({ mode, stateContainer } const element = ( = ({ mode, stateContainer } - {hitsStatus === FetchStatus.PARTIAL && - (mode === HitsCounterMode.standalone ? ( + {showGreaterOrEqualSign ? ( + mode === HitsCounterMode.standalone ? ( = ({ mode, stateContainer } defaultMessage="≥{formattedHits}" values={{ formattedHits }} /> - ))} - {hitsStatus !== FetchStatus.PARTIAL && - (mode === HitsCounterMode.standalone ? ( - - ) : ( - formattedHits - ))} + ) + ) : mode === HitsCounterMode.standalone ? ( + + ) : ( + formattedHits + )} @@ -103,6 +117,19 @@ export const HitsCounter: React.FC = ({ mode, stateContainer } /> )} + {hitsStatus === FetchStatus.ERROR && ( + + + + )} ); diff --git a/src/plugins/unified_histogram/public/chart/histogram.test.tsx b/src/plugins/unified_histogram/public/chart/histogram.test.tsx index 26bdc0c505234..72b5c0cc0b791 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.test.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.test.tsx @@ -240,7 +240,7 @@ describe('Histogram', () => { onLoad(false, adapters); }); expect(props.onTotalHitsChange).toHaveBeenLastCalledWith( - UnifiedHistogramFetchStatus.complete, + UnifiedHistogramFetchStatus.error, 100 ); expect(props.onChartLoad).toHaveBeenLastCalledWith({ adapters }); diff --git a/src/plugins/unified_histogram/public/chart/histogram.tsx b/src/plugins/unified_histogram/public/chart/histogram.tsx index faa5ddd2b1fc3..e63cf775158aa 100644 --- a/src/plugins/unified_histogram/public/chart/histogram.tsx +++ b/src/plugins/unified_histogram/public/chart/histogram.tsx @@ -130,9 +130,6 @@ export function Histogram({ | undefined; const response = json?.rawResponse; - // The response can have `response?._shards.failed` but we should still be able to show hits number - // TODO: show shards warnings as a badge next to the total hits number - if (requestFailed) { onTotalHitsChange?.(UnifiedHistogramFetchStatus.error, undefined); onChartLoad?.({ adapters: adapters ?? {} }); @@ -142,10 +139,14 @@ export function Histogram({ const adapterTables = adapters?.tables?.tables; const totalHits = computeTotalHits(hasLensSuggestions, adapterTables, isPlainRecord); - onTotalHitsChange?.( - isLoading ? UnifiedHistogramFetchStatus.loading : UnifiedHistogramFetchStatus.complete, - totalHits ?? hits?.total - ); + if (response?._shards?.failed || response?.timed_out) { + onTotalHitsChange?.(UnifiedHistogramFetchStatus.error, totalHits); + } else { + onTotalHitsChange?.( + isLoading ? UnifiedHistogramFetchStatus.loading : UnifiedHistogramFetchStatus.complete, + totalHits ?? hits?.total + ); + } if (response) { const newBucketInterval = buildBucketInterval({ diff --git a/test/functional/apps/discover/ccs_compatibility/_search_errors.ts b/test/functional/apps/discover/ccs_compatibility/_search_errors.ts index 7045e0e7d1a3b..96db6e2f7a347 100644 --- a/test/functional/apps/discover/ccs_compatibility/_search_errors.ts +++ b/test/functional/apps/discover/ccs_compatibility/_search_errors.ts @@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Ensure documents are still returned for the successful shards await retry.try(async function tryingForTime() { - const hitCount = await discover.getHitCount(); + const hitCount = await discover.getHitCount({ isPartial: true }); expect(hitCount).to.be('9,247'); }); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 979e4341931ab..e8a0de7fbc340 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -339,9 +339,11 @@ export class DiscoverPageObject extends FtrService { return await this.header.waitUntilLoadingHasFinished(); } - public async getHitCount() { + public async getHitCount({ isPartial }: { isPartial?: boolean } = {}) { await this.header.waitUntilLoadingHasFinished(); - return await this.testSubjects.getVisibleText('discoverQueryHits'); + return await this.testSubjects.getVisibleText( + isPartial ? 'discoverQueryHitsPartial' : 'discoverQueryHits' + ); } public async getHitCountInt() { From b8dbf835088295246f457d27daa44d2546e17565 Mon Sep 17 00:00:00 2001 From: Paulina Shakirova Date: Wed, 6 Nov 2024 13:50:19 +0100 Subject: [PATCH 10/53] feat: SavedObjectSaveModal - add postfix if the title is duplicated (#198777) ## Summary This PR fixes the issue with the [[Dashboard][Event annotations][Lens] appending a postfix when there is a duplicate title instead of interrupting the save flow](https://github.com/elastic/kibana/issues/161119). Currently, if a user wants to save an object as a new group without modifying the title, they will see a warning about it. This PR introduces an additional step. If the user attempts to save an object as a new group, we automatically append a postfix to the title. If the user manually changes the title back to the original and tries to save it, they will see the currently configured warning. https://github.com/user-attachments/assets/4f764b75-5b83-4277-8e5e-42fe1174fae7 --- .../saved_object_save_modal.test.tsx | 50 +++++++++++++++++++ .../save_modal/saved_object_save_modal.tsx | 21 ++++++++ 2 files changed, 71 insertions(+) diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx index 82e8051b897c7..273a79c307001 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx @@ -121,4 +121,54 @@ describe('SavedObjectSaveModal', () => { expect(onSave.mock.calls[0][0].newCopyOnSave).toBe(true); }); }); + + describe('handle title duplication logic', () => { + it('should append "[1]" to title if no number is present', async () => { + const onSave = jest.fn(); + + render( + + {}} + title="Saved Object" + objectType="visualization" + showDescription={true} + showCopyOnSave={true} + /> + + ); + + const switchElement = screen.getByTestId('saveAsNewCheckbox'); + await userEvent.click(switchElement); + + await waitFor(() => { + expect(screen.getByTestId('savedObjectTitle')).toHaveValue('Saved Object [1]'); + }); + }); + + it('should increment the number by one when a number is already present', async () => { + const onSave = jest.fn(); + + render( + + {}} + title="Saved Object [1]" + objectType="visualization" + showDescription={true} + showCopyOnSave={true} + /> + + ); + + const switchElement = screen.getByTestId('saveAsNewCheckbox'); + await userEvent.click(switchElement); + + await waitFor(() => { + expect(screen.getByTestId('savedObjectTitle')).toHaveValue('Saved Object [2]'); + }); + }); + }); }); diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index aa82feafb60aa..a3e6d1cc22b2a 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -276,7 +276,28 @@ export class SavedObjectSaveModal extends React.Component }); }; + private handleTitleDuplication = () => { + const regex = /\s*\[(\d+)\]$/; + const match = this.state.title.match(regex); + + if (match) { + const newNumber = Number(match[1]) + 1; + + this.setState({ + title: this.state.title.replace(regex, ` [${newNumber}]`), + }); + } else { + this.setState({ + title: this.state.title + ' [1]', + }); + } + }; + private onCopyOnSaveChange = (event: EuiSwitchEvent) => { + if (this.props.title === this.state.title && event.target.checked) { + this.handleTitleDuplication(); + } + this.setState({ copyOnSave: event.target.checked, }); From bdb6ff128c8e9fbf83d5e38e9a771853a171fdd2 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Wed, 6 Nov 2024 09:50:44 -0300 Subject: [PATCH 11/53] [Security Solution] `/upgrade/_perform` performance testing (#197898) ## Summary - Creates a new `withSyncSecuritySpan` wrapper to measure sync functions in APM. Adds this wrapper to new CPU intensive logic in the `/upgrade/_perform` endpoint. - Do performance testing on the endpoint. See results below. ## Performance testing ### Possible OOMs in production Serverless Created an Serverless instance and manually installed different Prebuilt Rules package to force rule upgrades. - With the current published prebuilt packages, a user cannot update more than 950 rules with a single request. - This number is expected to grow, but at a slower pace than the actual number of rules being published. - Also, as users start customizing rules, rules with conflicts will be excluded from bulk requests, which will **make payloads even smaller.** - Testing the biggest possible upgrade request, Serverless behaved reliably and no **timeouts** or **OOMs** occurred: | From version | To version | Rule Updates | Request time | |---------|--------|---------|--------| | 8.9.9 | 8.15.9 | 913 | 47.3s | | 8.9.12 | 8.15.9 | 917 | 52.34s | | 8.9.15 | 8.15.9 | 928 | 56.08s | | 8.10.4 | 8.15.9 | 872 | 43.29s | | 8.10.5 | 8.15.9 | 910 | 52.21s | | 8.10.6 | 8.15.9 | 913 | 55.92s | | 8.10.7 | 8.15.9 | 924 | 49.89s | | 8.11.2 | 8.15.9 | 910 | 56.48s | | 8.11.5 | 8.15.9 | 928 | 49.22s | | 8.11.16 | 8.15.9 | 695 | 38.91s | | 8.12.6 | 8.15.9 | 947 | 51.13s | | 8.13.11 | 8.15.9 | 646 | 42.98s | - Given the positive results for much bigger payloads seen in the **Memory profiling with limited memory in Kibana production mode** below, we can assume that there's no risk of OOMs in Serverless at the moment. ### Memory profiling with limited memory in Kibana production mode - Launched Kibana in Production mode, and set a **memory limit of 700mb** to mimic as closely as possible the Serverless environment (where memory is a hard constraint) - Stress tested with big number of requests and saw the following behaviour: | Rule Updates | Request time (min) | OOM error? | Metrics | |---------|--------|--------|--------| | 1500 | 1.1 | No |
Unfold![image](https://github.com/user-attachments/assets/46303a1a-a929-4c00-8777-8d1f23face17)
| | 2000 | 1.5 | No |
Unfold![image](https://github.com/user-attachments/assets/bd33d259-50fd-42df-947d-3a2e7c5c78c3)
| | 2500 | 1.8 | No |
Unfold![image](https://github.com/user-attachments/assets/9145d2e7-e87c-4ba6-8633-7fe1087c29fb)
| | 2750 | 1.9 | No |
Unfold![image](https://github.com/user-attachments/assets/9009163e-f58d-4be3-8a1f-87844760a037)
| | 3000 | - | YES | | - Rule upgrade OOM's consistently when the payload is >= 3000 rules, but behaves reliably below that. Good enough buffer for growth of the Prebuilt Rules package. - Also, the saw-toothed shape of the heap used graphics shows that garbage collection works properly for payloads under 3000 rules. ### APM request profiling - Connected Kibana in production mode to a APM server to measure spans of the `/upgrade/_perform` request. - Additionally, measured new CPU-intensive logic which calculates rule diffs and create rule payloads for upgrades. - An example span for a successful upgrade of 2500 rules: image - The new spans for CPU-intensive tasks `getUpgradeableRules` and `createModifiedPrebuiltRuleAssets`, which are displayed as `blocking`, have an acceptable span length, and do not have a considerable overhead on the total length of the request. ### Timeout testing - Tested Kibana with `--no-base-path` in order to check for potential timeouts in long running requests (ESS Cloud proxy is supposed to have a request timeout config of 2.5 mins) - Still [confirming](https://elastic.slack.com/archives/C5UDAFZQU/p1730297621776729) with Kibana Operations the behaviour of the timeouts in ESS and Serverless envs: - Tested with mock rules (indexed directly to ES) and **no timeouts occurred**: | Rule Updates | Request time (min) | |---------|--------| | 2000 | 2.1 | | 2000 | 2.1 | | 2250 | 2.3 | | 2500 | 2.6 | | 3000 | 3.1 | ### Conclusion The results show that the `/upgrade/_perform` endpoint performs reliably under stress, given the currentexpected request payloads. The only question to triple check is the behaviour of server request timeouts in Serverless: I'm waiting the Kibana ops team to get back to me, even though testing here did not show cases of timeouts. --------- Co-authored-by: Elastic Machine Co-authored-by: Dmitrii Shevchenko --- .../create_upgradeable_rules_payload.ts | 142 +++++++++--------- .../get_upgradeable_rules.ts | 102 +++++++------ .../server/utils/with_security_span.ts | 15 +- 3 files changed, 140 insertions(+), 119 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts index 97e587646e524..b25320e1131ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts @@ -5,6 +5,7 @@ * 2.0. */ import { pickBy } from 'lodash'; +import { withSecuritySpanSync } from '../../../../../utils/with_security_span'; import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { PickVersionValuesEnum, @@ -36,77 +37,82 @@ export const createModifiedPrebuiltRuleAssets = ({ upgradeableRules, requestBody, }: CreateModifiedPrebuiltRuleAssetsProps) => { - const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; - - const { modifiedPrebuiltRuleAssets, processingErrors } = upgradeableRules.reduce( - (processedRules, upgradeableRule) => { - const targetRuleType = upgradeableRule.target.type; - const ruleId = upgradeableRule.target.rule_id; - const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); - - try { - if (fieldNames === undefined) { - throw new Error(`Unexpected rule type: ${targetRuleType}`); - } - - const { current, target } = upgradeableRule; - if (current.type !== target.type) { - assertPickVersionIsTarget({ ruleId, requestBody }); - } - - const calculatedRuleDiff = calculateRuleFieldsDiff({ - base_version: upgradeableRule.base - ? convertRuleToDiffable(convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base)) - : MissingVersion, - current_version: convertRuleToDiffable(upgradeableRule.current), - target_version: convertRuleToDiffable( - convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) - ), - }) as AllFieldsDiff; - - if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { - const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); - if (fieldsWithConflicts.length > 0) { - // If the mode is ALL_RULES, no fields can be overriden to any other pick_version - // than "MERGED", so throw an error for the fields that have conflicts. - throw new Error( - `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( - ', ' - )}. Please resolve the conflict manually or choose another value for 'pick_version'` - ); + return withSecuritySpanSync(createModifiedPrebuiltRuleAssets.name, () => { + const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; + + const { modifiedPrebuiltRuleAssets, processingErrors } = + upgradeableRules.reduce( + (processedRules, upgradeableRule) => { + const targetRuleType = upgradeableRule.target.type; + const ruleId = upgradeableRule.target.rule_id; + const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); + + try { + if (fieldNames === undefined) { + throw new Error(`Unexpected rule type: ${targetRuleType}`); + } + + const { current, target } = upgradeableRule; + if (current.type !== target.type) { + assertPickVersionIsTarget({ ruleId, requestBody }); + } + + const calculatedRuleDiff = calculateRuleFieldsDiff({ + base_version: upgradeableRule.base + ? convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base) + ) + : MissingVersion, + current_version: convertRuleToDiffable(upgradeableRule.current), + target_version: convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) + ), + }) as AllFieldsDiff; + + if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { + const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); + if (fieldsWithConflicts.length > 0) { + // If the mode is ALL_RULES, no fields can be overriden to any other pick_version + // than "MERGED", so throw an error for the fields that have conflicts. + throw new Error( + `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( + ', ' + )}. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + } + } + + const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ + upgradeableRule, + fieldNames, + requestBody, + globalPickVersion, + calculatedRuleDiff, + }); + + processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); + + return processedRules; + } catch (err) { + processedRules.processingErrors.push({ + error: err, + item: { rule_id: ruleId }, + }); + + return processedRules; } + }, + { + modifiedPrebuiltRuleAssets: [], + processingErrors: [], } + ); - const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ - upgradeableRule, - fieldNames, - requestBody, - globalPickVersion, - calculatedRuleDiff, - }); - - processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); - - return processedRules; - } catch (err) { - processedRules.processingErrors.push({ - error: err, - item: { rule_id: ruleId }, - }); - - return processedRules; - } - }, - { - modifiedPrebuiltRuleAssets: [], - processingErrors: [], - } - ); - - return { - modifiedPrebuiltRuleAssets, - processingErrors, - }; + return { + modifiedPrebuiltRuleAssets, + processingErrors, + }; + }); }; interface CreateModifiedPrebuiltRuleAssetParams { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts index acfdb674c309a..750561b9858a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { withSecuritySpanSync } from '../../../../../utils/with_security_span'; import type { RuleResponse, RuleUpgradeSpecifier, @@ -26,58 +26,60 @@ export const getUpgradeableRules = ({ versionSpecifiers?: RuleUpgradeSpecifier[]; mode: Mode; }) => { - const upgradeableRules = new Map( - rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) - ); - const fetchErrors: Array> = []; - const skippedRules: SkippedRuleUpgrade[] = []; + return withSecuritySpanSync(getUpgradeableRules.name, () => { + const upgradeableRules = new Map( + rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) + ); + const fetchErrors: Array> = []; + const skippedRules: SkippedRuleUpgrade[] = []; - if (mode === ModeEnum.SPECIFIC_RULES) { - const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); - const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); - versionSpecifiers?.forEach((rule) => { - // Check that the requested rule was found - if (!installedRuleIds.has(rule.rule_id)) { - fetchErrors.push({ - error: new Error( - `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` - ), - item: rule, - }); - return; - } + if (mode === ModeEnum.SPECIFIC_RULES) { + const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); + const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); + versionSpecifiers?.forEach((rule) => { + // Check that the requested rule was found + if (!installedRuleIds.has(rule.rule_id)) { + fetchErrors.push({ + error: new Error( + `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` + ), + item: rule, + }); + return; + } - // Check that the requested rule is upgradeable - if (!upgradeableRuleIds.has(rule.rule_id)) { - skippedRules.push({ - rule_id: rule.rule_id, - reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, - }); - return; - } + // Check that the requested rule is upgradeable + if (!upgradeableRuleIds.has(rule.rule_id)) { + skippedRules.push({ + rule_id: rule.rule_id, + reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, + }); + return; + } - // Check that rule revisions match (no update slipped in since the user reviewed the list) - const currentRevision = currentRules.find( - (currentRule) => currentRule.rule_id === rule.rule_id - )?.revision; - if (rule.revision !== currentRevision) { - fetchErrors.push({ - error: new Error( - `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` - ), - item: rule, - }); - // Remove the rule from the list of upgradeable rules - if (upgradeableRules.has(rule.rule_id)) { - upgradeableRules.delete(rule.rule_id); + // Check that rule revisions match (no update slipped in since the user reviewed the list) + const currentRevision = currentRules.find( + (currentRule) => currentRule.rule_id === rule.rule_id + )?.revision; + if (rule.revision !== currentRevision) { + fetchErrors.push({ + error: new Error( + `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` + ), + item: rule, + }); + // Remove the rule from the list of upgradeable rules + if (upgradeableRules.has(rule.rule_id)) { + upgradeableRules.delete(rule.rule_id); + } } - } - }); - } + }); + } - return { - upgradeableRules: Array.from(upgradeableRules.values()), - fetchErrors, - skippedRules, - }; + return { + upgradeableRules: Array.from(upgradeableRules.values()), + fetchErrors, + skippedRules, + }; + }); }; diff --git a/x-pack/plugins/security_solution/server/utils/with_security_span.ts b/x-pack/plugins/security_solution/server/utils/with_security_span.ts index 58787dc45d09b..f9f78600cfb8d 100644 --- a/x-pack/plugins/security_solution/server/utils/with_security_span.ts +++ b/x-pack/plugins/security_solution/server/utils/with_security_span.ts @@ -6,7 +6,7 @@ */ import type { SpanOptions } from '@kbn/apm-utils'; import { withSpan } from '@kbn/apm-utils'; -import type agent from 'elastic-apm-node'; +import agent from 'elastic-apm-node'; import { APP_ID } from '../../common/constants'; type Span = Exclude; @@ -35,3 +35,16 @@ export const withSecuritySpan = ( }, cb ); + +export const withSecuritySpanSync = (name: string, fn: (span: Span | null) => T): T => { + const span = agent.startSpan(name, APP_ID); + + try { + const result = fn(span); + return result; + } finally { + if (span) { + span.end(); + } + } +}; From ce9f6222d8a3408bf72e8e3118d0e98be166657b Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 6 Nov 2024 08:01:46 -0500 Subject: [PATCH 12/53] [Fleet] remove deprecated settings API endpoints (#198799) --- oas_docs/bundle.json | 243 +----------------- oas_docs/bundle.serverless.json | 243 +----------------- oas_docs/output/kibana.serverless.yaml | 172 ------------- oas_docs/output/kibana.yaml | 172 ------------- .../plugins/fleet/common/constants/routes.ts | 5 - .../fleet/common/types/models/settings.ts | 1 - .../types/rest_spec/enrollment_api_key.ts | 5 +- .../common/types/rest_spec/health_check.ts | 4 - .../epm/screens/detail/index.test.tsx | 8 +- .../use_create_azure_arm_template_url.ts | 7 +- .../hooks/use_create_cloud_shell_url.ts | 6 +- .../routes/enrollment_api_key/handler.ts | 1 - .../server/routes/enrollment_api_key/index.ts | 73 +----- .../routes/health_check/handler.test.ts | 16 -- .../server/routes/health_check/handler.ts | 11 +- .../routes/settings/settings_handler.test.ts | 2 - .../fleet/server/services/settings.test.ts | 4 +- .../plugins/fleet/server/services/settings.ts | 11 +- .../server/types/rest_spec/health_check.ts | 9 - .../fleet/server/types/rest_spec/settings.ts | 10 - .../apis/enrollment_api_keys/crud.ts | 27 -- .../apis/fleet_telemetry.ts | 2 +- .../apis/settings/get.ts | 26 +- .../apis/settings/index.js | 1 - .../apis/settings/update.ts | 135 ---------- 25 files changed, 24 insertions(+), 1170 deletions(-) delete mode 100644 x-pack/test/fleet_api_integration/apis/settings/update.ts diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 743d6ae6e422a..d378331392dc0 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -16887,176 +16887,6 @@ ] } }, - "/api/fleet/enrollment-api-keys": { - "get": { - "operationId": "get-fleet-enrollment-api-keys-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "type": "number" - } - }, - { - "in": "query", - "name": "perPage", - "required": false, - "schema": { - "default": 20, - "type": "number" - } - }, - { - "in": "query", - "name": "kuery", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - }, - "post": { - "operationId": "post-fleet-enrollment-api-keys-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "expiration": { - "type": "string" - }, - "name": { - "type": "string" - }, - "policy_id": { - "type": "string" - } - }, - "required": [ - "policy_id" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - } - }, - "/api/fleet/enrollment-api-keys/{keyId}": { - "delete": { - "operationId": "delete-fleet-enrollment-api-keys-keyid-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "keyId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - }, - "get": { - "operationId": "get-fleet-enrollment-api-keys-keyid-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "keyId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - } - }, "/api/fleet/enrollment_api_keys": { "get": { "description": "List enrollment API keys", @@ -17150,49 +16980,6 @@ }, "type": "array" }, - "list": { - "deprecated": true, - "items": { - "additionalProperties": false, - "properties": { - "active": { - "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.", - "type": "string" - }, - "api_key_id": { - "description": "The ID of the API key in the Security API.", - "type": "string" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "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.", - "type": "string" - } - }, - "required": [ - "id", - "api_key_id", - "api_key", - "active", - "created_at" - ], - "type": "object" - }, - "type": "array" - }, "page": { "type": "number" }, @@ -17207,8 +16994,7 @@ "items", "total", "page", - "perPage", - "list" + "perPage" ], "type": "object" } @@ -24920,10 +24706,6 @@ "schema": { "additionalProperties": false, "properties": { - "host": { - "format": "uri", - "type": "string" - }, "id": { "type": "string" } @@ -24943,10 +24725,6 @@ "schema": { "additionalProperties": false, "properties": { - "host": { - "deprecated": true, - "type": "string" - }, "host_id": { "type": "string" }, @@ -39316,12 +39094,6 @@ ], "type": "object" }, - "fleet_server_hosts": { - "items": { - "type": "string" - }, - "type": "array" - }, "has_seen_add_data_notice": { "type": "boolean" }, @@ -39476,13 +39248,6 @@ ], "type": "object" }, - "fleet_server_hosts": { - "items": { - "format": "uri", - "type": "string" - }, - "type": "array" - }, "has_seen_add_data_notice": { "type": "boolean" }, @@ -39531,12 +39296,6 @@ ], "type": "object" }, - "fleet_server_hosts": { - "items": { - "type": "string" - }, - "type": "array" - }, "has_seen_add_data_notice": { "type": "boolean" }, diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index f171dadde991a..972712045a1c6 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -16887,176 +16887,6 @@ ] } }, - "/api/fleet/enrollment-api-keys": { - "get": { - "operationId": "get-fleet-enrollment-api-keys-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "query", - "name": "page", - "required": false, - "schema": { - "default": 1, - "type": "number" - } - }, - { - "in": "query", - "name": "perPage", - "required": false, - "schema": { - "default": 20, - "type": "number" - } - }, - { - "in": "query", - "name": "kuery", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - }, - "post": { - "operationId": "post-fleet-enrollment-api-keys-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "expiration": { - "type": "string" - }, - "name": { - "type": "string" - }, - "policy_id": { - "type": "string" - } - }, - "required": [ - "policy_id" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - } - }, - "/api/fleet/enrollment-api-keys/{keyId}": { - "delete": { - "operationId": "delete-fleet-enrollment-api-keys-keyid-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "keyId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - }, - "get": { - "operationId": "get-fleet-enrollment-api-keys-keyid-2", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "keyId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - } - }, "/api/fleet/enrollment_api_keys": { "get": { "description": "List enrollment API keys", @@ -17150,49 +16980,6 @@ }, "type": "array" }, - "list": { - "deprecated": true, - "items": { - "additionalProperties": false, - "properties": { - "active": { - "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.", - "type": "string" - }, - "api_key_id": { - "description": "The ID of the API key in the Security API.", - "type": "string" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "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.", - "type": "string" - } - }, - "required": [ - "id", - "api_key_id", - "api_key", - "active", - "created_at" - ], - "type": "object" - }, - "type": "array" - }, "page": { "type": "number" }, @@ -17207,8 +16994,7 @@ "items", "total", "page", - "perPage", - "list" + "perPage" ], "type": "object" } @@ -24920,10 +24706,6 @@ "schema": { "additionalProperties": false, "properties": { - "host": { - "format": "uri", - "type": "string" - }, "id": { "type": "string" } @@ -24943,10 +24725,6 @@ "schema": { "additionalProperties": false, "properties": { - "host": { - "deprecated": true, - "type": "string" - }, "host_id": { "type": "string" }, @@ -39316,12 +39094,6 @@ ], "type": "object" }, - "fleet_server_hosts": { - "items": { - "type": "string" - }, - "type": "array" - }, "has_seen_add_data_notice": { "type": "boolean" }, @@ -39476,13 +39248,6 @@ ], "type": "object" }, - "fleet_server_hosts": { - "items": { - "format": "uri", - "type": "string" - }, - "type": "array" - }, "has_seen_add_data_notice": { "type": "boolean" }, @@ -39531,12 +39296,6 @@ ], "type": "object" }, - "fleet_server_hosts": { - "items": { - "type": "string" - }, - "type": "array" - }, "has_seen_add_data_notice": { "type": "boolean" }, diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index afb7d8bbd5f4d..a1cc60cbe7bd0 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -17841,44 +17841,6 @@ paths: - active - created_at type: array - list: - deprecated: true - items: - additionalProperties: false - type: object - properties: - active: - 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. - type: string - api_key_id: - description: The ID of the API key in the Security API. - type: string - created_at: - type: string - id: - type: string - name: - 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. - type: string - required: - - id - - api_key_id - - api_key - - active - - created_at - type: array page: type: number perPage: @@ -17890,7 +17852,6 @@ paths: - total - page - perPage - - list '400': content: application/json; Elastic-Api-Version=2023-10-31: @@ -18152,120 +18113,6 @@ paths: summary: '' tags: - Fleet enrollment API keys - /api/fleet/enrollment-api-keys: - get: - operationId: get-fleet-enrollment-api-keys-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: query - name: page - required: false - schema: - default: 1 - type: number - - in: query - name: perPage - required: false - schema: - default: 20 - type: number - - in: query - name: kuery - required: false - schema: - type: string - responses: {} - summary: '' - tags: [] - post: - operationId: post-fleet-enrollment-api-keys-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - expiration: - type: string - name: - type: string - policy_id: - type: string - required: - - policy_id - responses: {} - summary: '' - tags: [] - /api/fleet/enrollment-api-keys/{keyId}: - delete: - operationId: delete-fleet-enrollment-api-keys-keyid-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: keyId - required: true - schema: - type: string - responses: {} - summary: '' - tags: [] - get: - operationId: get-fleet-enrollment-api-keys-keyid-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: path - name: keyId - required: true - schema: - type: string - responses: {} - summary: '' - tags: [] /api/fleet/epm/bulk_assets: post: description: Bulk get assets @@ -23280,9 +23127,6 @@ paths: additionalProperties: false type: object properties: - host: - format: uri - type: string id: type: string required: @@ -23295,9 +23139,6 @@ paths: additionalProperties: false type: object properties: - host: - deprecated: true - type: string host_id: type: string name: @@ -33088,10 +32929,6 @@ paths: required: - enabled - is_preconfigured - fleet_server_hosts: - items: - type: string - type: array has_seen_add_data_notice: type: boolean id: @@ -33191,11 +33028,6 @@ paths: required: - enabled - is_preconfigured - fleet_server_hosts: - items: - format: uri - type: string - type: array has_seen_add_data_notice: type: boolean kibana_ca_sha256: @@ -33230,10 +33062,6 @@ paths: required: - enabled - is_preconfigured - fleet_server_hosts: - items: - type: string - type: array has_seen_add_data_notice: type: boolean id: diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 6122607df925f..0dd1586ef8b37 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -21274,44 +21274,6 @@ paths: - active - created_at type: array - list: - deprecated: true - items: - additionalProperties: false - type: object - properties: - active: - 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. - type: string - api_key_id: - description: The ID of the API key in the Security API. - type: string - created_at: - type: string - id: - type: string - name: - 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. - type: string - required: - - id - - api_key_id - - api_key - - active - - created_at - type: array page: type: number perPage: @@ -21323,7 +21285,6 @@ paths: - total - page - perPage - - list '400': content: application/json; Elastic-Api-Version=2023-10-31: @@ -21585,120 +21546,6 @@ paths: summary: '' tags: - Fleet enrollment API keys - /api/fleet/enrollment-api-keys: - get: - operationId: get-fleet-enrollment-api-keys-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: query - name: page - required: false - schema: - default: 1 - type: number - - in: query - name: perPage - required: false - schema: - default: 20 - type: number - - in: query - name: kuery - required: false - schema: - type: string - responses: {} - summary: '' - tags: [] - post: - operationId: post-fleet-enrollment-api-keys-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - expiration: - type: string - name: - type: string - policy_id: - type: string - required: - - policy_id - responses: {} - summary: '' - tags: [] - /api/fleet/enrollment-api-keys/{keyId}: - delete: - operationId: delete-fleet-enrollment-api-keys-keyid-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: keyId - required: true - schema: - type: string - responses: {} - summary: '' - tags: [] - get: - operationId: get-fleet-enrollment-api-keys-keyid-2 - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: path - name: keyId - required: true - schema: - type: string - responses: {} - summary: '' - tags: [] /api/fleet/epm/bulk_assets: post: description: Bulk get assets @@ -26713,9 +26560,6 @@ paths: additionalProperties: false type: object properties: - host: - format: uri - type: string id: type: string required: @@ -26728,9 +26572,6 @@ paths: additionalProperties: false type: object properties: - host: - deprecated: true - type: string host_id: type: string name: @@ -36521,10 +36362,6 @@ paths: required: - enabled - is_preconfigured - fleet_server_hosts: - items: - type: string - type: array has_seen_add_data_notice: type: boolean id: @@ -36624,11 +36461,6 @@ paths: required: - enabled - is_preconfigured - fleet_server_hosts: - items: - format: uri - type: string - type: array has_seen_add_data_notice: type: boolean kibana_ca_sha256: @@ -36663,10 +36495,6 @@ paths: required: - enabled - is_preconfigured - fleet_server_hosts: - items: - type: string - type: array has_seen_add_data_notice: type: boolean id: diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 61d50d0f9e073..4b40b4fb51092 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -171,11 +171,6 @@ export const ENROLLMENT_API_KEY_ROUTES = { LIST_PATTERN: `${API_ROOT}/enrollment_api_keys`, INFO_PATTERN: `${API_ROOT}/enrollment_api_keys/{keyId}`, DELETE_PATTERN: `${API_ROOT}/enrollment_api_keys/{keyId}`, - // deprecated since 8.0 - CREATE_PATTERN_DEPRECATED: `${API_ROOT}/enrollment-api-keys`, - LIST_PATTERN_DEPRECATED: `${API_ROOT}/enrollment-api-keys`, - INFO_PATTERN_DEPRECATED: `${API_ROOT}/enrollment-api-keys/{keyId}`, - DELETE_PATTERN_DEPRECATED: `${API_ROOT}/enrollment-api-keys/{keyId}`, }; export const UNINSTALL_TOKEN_ROUTES = { diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index a63211ef14c55..15b74645b16b3 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -7,7 +7,6 @@ export interface BaseSettings { has_seen_add_data_notice?: boolean; - fleet_server_hosts?: string[]; prerelease_integrations_enabled?: boolean; } diff --git a/x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts index 7fa724e5079c8..e1509d551bdef 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/enrollment_api_key.ts @@ -13,10 +13,7 @@ export interface GetEnrollmentAPIKeysRequest { query: ListWithKuery; } -export type GetEnrollmentAPIKeysResponse = ListResult & { - // deprecated in 8.x - list?: EnrollmentAPIKey[]; -}; +export type GetEnrollmentAPIKeysResponse = ListResult; export interface GetOneEnrollmentAPIKeyRequest { params: { diff --git a/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts b/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts index 4b25d958d21ad..5554dde4e2484 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts @@ -8,14 +8,10 @@ export interface PostHealthCheckRequest { body: { id: string; - /** @deprecated use id field instead */ - host?: string; }; } export interface PostHealthCheckResponse { host_id: string; - // deprecated - host?: string; status: string; } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index 3cd1a310208bf..6c9be4796f205 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -106,7 +106,7 @@ describe('When on integration detail', () => { describe('and the package is not installed and prerelease enabled', () => { beforeEach(async () => { mockedApi.responseProvider.getSettings.mockReturnValue({ - item: { prerelease_integrations_enabled: true, id: '', fleet_server_hosts: [] }, + item: { prerelease_integrations_enabled: true, id: '' }, }); mockGAAndPrereleaseVersions('1.0.0-beta'); await render(); @@ -145,7 +145,7 @@ describe('When on integration detail', () => { beforeEach(async () => { mockGAAndPrereleaseVersions('1.0.0'); mockedApi.responseProvider.getSettings.mockReturnValue({ - item: { prerelease_integrations_enabled: false, id: '', fleet_server_hosts: [] }, + item: { prerelease_integrations_enabled: false, id: '' }, }); await render(); await act(() => mockedApi.waitForApi()); @@ -172,7 +172,7 @@ describe('When on integration detail', () => { describe('and a custom UI extension is NOT registered', () => { beforeEach(async () => { mockedApi.responseProvider.getSettings.mockReturnValue({ - item: { prerelease_integrations_enabled: false, id: '', fleet_server_hosts: [] }, + item: { prerelease_integrations_enabled: false, id: '' }, }); await render(); await act(() => mockedApi.waitForApi()); @@ -211,7 +211,7 @@ describe('When on integration detail', () => { beforeEach(async () => { let setWasRendered: () => void; mockedApi.responseProvider.getSettings.mockReturnValue({ - item: { prerelease_integrations_enabled: false, id: '', fleet_server_hosts: [] }, + item: { prerelease_integrations_enabled: false, id: '' }, }); lazyComponentWasRendered = new Promise((resolve) => { setWasRendered = resolve; diff --git a/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_azure_arm_template_url.ts b/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_azure_arm_template_url.ts index ea20d14fdb868..47237ac962bfd 100644 --- a/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_azure_arm_template_url.ts +++ b/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_azure_arm_template_url.ts @@ -7,10 +7,9 @@ import { i18n } from '@kbn/i18n'; +import { useGetFleetServerHosts } from '../../../hooks'; import type { AzureArmTemplateProps } from '../../agent_enrollment_flyout/types'; -import { useGetSettings } from '../../../hooks'; - const ARM_TEMPLATE_DEFAULT_ACCOUNT_TYPE = 'single-account'; export const useCreateAzureArmTemplateUrl = ({ @@ -20,13 +19,13 @@ export const useCreateAzureArmTemplateUrl = ({ enrollmentAPIKey: string | undefined; azureArmTemplateProps: AzureArmTemplateProps | undefined; }) => { - const { data, isLoading } = useGetSettings(); + const { data, isLoading } = useGetFleetServerHosts(); let isError = false; let error: string | undefined; // Default fleet server host - const fleetServerHost = data?.item.fleet_server_hosts?.[0]; + const fleetServerHost = data?.items?.find((item) => item.is_default)?.host_urls?.[0]; if (!fleetServerHost && !isLoading) { isError = true; diff --git a/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_cloud_shell_url.ts b/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_cloud_shell_url.ts index f1543e337180b..34ab224b2028c 100644 --- a/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_cloud_shell_url.ts +++ b/x-pack/plugins/fleet/public/components/cloud_security_posture/hooks/use_create_cloud_shell_url.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { PackagePolicy } from '../../../../common'; -import { useGetSettings } from '../../../hooks'; +import { useGetFleetServerHosts } from '../../../hooks'; import { getCloudShellUrlFromPackagePolicy } from '../services'; @@ -20,13 +20,13 @@ export const useCreateCloudShellUrl = ({ enrollmentAPIKey: string | undefined; packagePolicy?: PackagePolicy; }) => { - const { data, isLoading } = useGetSettings(); + const { data, isLoading } = useGetFleetServerHosts(); let isError = false; let error: string | undefined; // Default fleet server host - const fleetServerHost = data?.item.fleet_server_hosts?.[0]; + const fleetServerHost = data?.items?.find((item) => item.is_default)?.host_urls?.[0]; if (!fleetServerHost && !isLoading) { isError = true; diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index a38f5bdadc617..cfce60b0f18f5 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -43,7 +43,6 @@ export const getEnrollmentApiKeysHandler: RequestHandler< spaceId: useSpaceAwareness ? getCurrentNamespace(soClient) : undefined, }); const body: GetEnrollmentAPIKeysResponse = { - list: items, // deprecated items, total, page, diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts index bcf4448420919..bc6c61dc8ffe4 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/index.ts @@ -109,10 +109,7 @@ export const registerRoutes = (router: FleetAuthzRouter) => { request: GetEnrollmentAPIKeysRequestSchema, response: { 200: { - body: () => - ListResponseSchema(EnrollmentAPIKeySchema).extends({ - list: schema.arrayOf(EnrollmentAPIKeySchema, { meta: { deprecated: true } }), - }), + body: () => ListResponseSchema(EnrollmentAPIKeySchema), }, 400: { body: genericErrorResponse, @@ -154,72 +151,4 @@ export const registerRoutes = (router: FleetAuthzRouter) => { }, postEnrollmentApiKeyHandler ); - - router.versioned - .get({ - path: ENROLLMENT_API_KEY_ROUTES.INFO_PATTERN_DEPRECATED, - fleetAuthz: { - fleet: { readEnrollmentTokens: true }, - }, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: GetOneEnrollmentAPIKeyRequestSchema }, - }, - getOneEnrollmentApiKeyHandler - ); - - router.versioned - .delete({ - path: ENROLLMENT_API_KEY_ROUTES.DELETE_PATTERN_DEPRECATED, - fleetAuthz: { - fleet: { allAgents: true }, - }, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: DeleteEnrollmentAPIKeyRequestSchema }, - }, - deleteEnrollmentApiKeyHandler - ); - - router.versioned - .get({ - path: ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN_DEPRECATED, - fleetAuthz: { - fleet: { readEnrollmentTokens: true }, - }, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: GetEnrollmentAPIKeysRequestSchema }, - }, - getEnrollmentApiKeysHandler - ); - - router.versioned - .post({ - path: ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN_DEPRECATED, - fleetAuthz: { - fleet: { allAgents: true }, - }, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: PostEnrollmentAPIKeyRequestSchema }, - }, - postEnrollmentApiKeyHandler - ); }; diff --git a/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts b/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts index bb36c2ec8146b..37c204c141a04 100644 --- a/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts +++ b/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts @@ -63,21 +63,6 @@ describe('Fleet server health_check handler', () => { }); }); - it('should return a bad request error if body contains deprecated parameter `host`', async () => { - const res = await postHealthCheckHandler( - mockContext, - { body: { host: 'https://localhost:8220' } } as any, - mockResponse as any - ); - - expect(res).toEqual({ - body: { - message: `Property 'host' is deprecated. Please use id instead.`, - }, - statusCode: 400, - }); - }); - it('should return 200 and active status when fetch response is `active`', async () => { const activeRes = { status: 'ONLINE', @@ -105,7 +90,6 @@ describe('Fleet server health_check handler', () => { ); const expectedResponse = { - host: 'https://localhost:8220', host_id: 'default-fleet-server', name: 'Default', status: 'ONLINE', diff --git a/x-pack/plugins/fleet/server/routes/health_check/handler.ts b/x-pack/plugins/fleet/server/routes/health_check/handler.ts index f191caa3fff1b..16114217bee2b 100644 --- a/x-pack/plugins/fleet/server/routes/health_check/handler.ts +++ b/x-pack/plugins/fleet/server/routes/health_check/handler.ts @@ -20,17 +20,10 @@ export const postHealthCheckHandler: FleetRequestHandler< TypeOf > = async (context, request, response) => { const abortController = new AbortController(); - const { id, host: deprecatedField } = request.body; + const { id } = request.body; const coreContext = await context.core; const soClient = coreContext.savedObjects.client; - if (deprecatedField) { - return response.badRequest({ - body: { - message: `Property 'host' is deprecated. Please use id instead.`, - }, - }); - } try { const fleetServerHost = await getFleetServerHost(soClient, id); @@ -61,7 +54,7 @@ export const postHealthCheckHandler: FleetRequestHandler< signal: abortController.signal, }); const bodyRes = await res.json(); - const body = { ...bodyRes, host }; + const body = { ...bodyRes }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts b/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts index 73641bfaed71a..151a0a2ba2af8 100644 --- a/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts +++ b/x-pack/plugins/fleet/server/routes/settings/settings_handler.test.ts @@ -29,7 +29,6 @@ jest.mock('../../services', () => ({ secret_storage_requirements_met: true, output_secret_storage_requirements_met: true, has_seen_add_data_notice: true, - fleet_server_hosts: ['http://localhost:8220'], prerelease_integrations_enabled: true, delete_unenrolled_agents: { enabled: true, @@ -76,7 +75,6 @@ describe('SettingsHandler', () => { secret_storage_requirements_met: true, output_secret_storage_requirements_met: true, has_seen_add_data_notice: true, - fleet_server_hosts: ['http://localhost:8220'], prerelease_integrations_enabled: true, delete_unenrolled_agents: { enabled: true, diff --git a/x-pack/plugins/fleet/server/services/settings.test.ts b/x-pack/plugins/fleet/server/services/settings.test.ts index 92fb85a335775..f88e735dfcb69 100644 --- a/x-pack/plugins/fleet/server/services/settings.test.ts +++ b/x-pack/plugins/fleet/server/services/settings.test.ts @@ -151,7 +151,7 @@ describe('saveSettings', () => { const soClient = savedObjectsClientMock.create(); const newData: Partial> = { - fleet_server_hosts: ['http://localhost:8220'], + output_secret_storage_requirements_met: true, }; soClient.find.mockResolvedValueOnce({ @@ -205,7 +205,7 @@ describe('saveSettings', () => { const soClient = savedObjectsClientMock.create(); const newData: Partial> = { - fleet_server_hosts: ['http://localhost:8220'], + output_secret_storage_requirements_met: true, }; soClient.find.mockRejectedValueOnce(Boom.notFound('not found')); diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 5f7433403c4e6..3288ec1090e41 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -7,8 +7,8 @@ import Boom from '@hapi/boom'; import type { SavedObjectsClientContract, SavedObjectsUpdateOptions } from '@kbn/core/server'; +import { omit } from 'lodash'; -import { normalizeHostsForAgents } from '../../common/services'; import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_ID } from '../../common/constants'; import type { Settings, BaseSettings } from '../../common/types'; import type { SettingsSOAttributes } from '../types'; @@ -16,7 +16,6 @@ import type { SettingsSOAttributes } from '../types'; import { DeleteUnenrolledAgentsPreconfiguredError } from '../errors'; import { appContextService } from './app_context'; -import { listFleetServerHosts } from './fleet_server_host'; import { auditLoggingService } from './audit_logging'; export async function getSettings(soClient: SavedObjectsClientContract): Promise { @@ -33,7 +32,6 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise throw Boom.notFound('Global settings not found'); } const settingsSo = res.saved_objects[0]; - const fleetServerHosts = await listFleetServerHosts(soClient); return { id: settingsSo.id, @@ -47,7 +45,6 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise settingsSo.attributes.use_space_awareness_migration_status, use_space_awareness_migration_started_at: settingsSo.attributes.use_space_awareness_migration_started_at, - fleet_server_hosts: fleetServerHosts.items.flatMap((item) => item.host_urls), preconfigured_fields: getConfigFleetServerHosts() ? ['fleet_server_hosts'] : [], delete_unenrolled_agents: settingsSo.attributes.delete_unenrolled_agents, }; @@ -88,10 +85,8 @@ export async function saveSettings( fromSetup?: boolean; } ): Promise & Pick> { - const data = { ...newData }; - if (data.fleet_server_hosts) { - data.fleet_server_hosts = data.fleet_server_hosts.map(normalizeHostsForAgents); - } + const data = omit({ ...newData }, 'fleet_server_hosts'); + const { createWithOverwrite, ...updateOptions } = options ?? {}; try { diff --git a/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts b/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts index 72c40e5bd6a68..c83d516c67f5a 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts @@ -10,20 +10,11 @@ import { schema } from '@kbn/config-schema'; export const PostHealthCheckRequestSchema = { body: schema.object({ id: schema.string(), - // deprecated - host: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), }), }; export const PostHealthCheckResponseSchema = schema.object({ status: schema.string(), name: schema.maybe(schema.string()), - host: schema.maybe( - schema.string({ - meta: { - deprecated: true, - }, - }) - ), host_id: schema.maybe(schema.string()), }); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts index 459070fa9591a..c40dcc0b63596 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts @@ -17,15 +17,6 @@ export const GetSettingsRequestSchema = {}; export const PutSettingsRequestSchema = { body: schema.object({ - fleet_server_hosts: schema.maybe( - schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }), { - validate: (value) => { - if (value.length && isDiffPathProtocol(value)) { - return 'Protocol and path must be the same for each URL'; - } - }, - }) - ), has_seen_add_data_notice: schema.maybe(schema.boolean()), additional_yaml_config: schema.maybe(schema.string()), // Deprecated not used @@ -61,7 +52,6 @@ export const SpaceSettingsResponseSchema = schema.object({ export const SettingsResponseSchema = schema.object({ item: schema.object({ has_seen_add_data_notice: schema.maybe(schema.boolean()), - fleet_server_hosts: schema.maybe(schema.arrayOf(schema.string())), prerelease_integrations_enabled: schema.maybe(schema.boolean()), id: schema.string(), version: schema.maybe(schema.string()), diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts index 9cb11df2b34de..24d1520d8d6f8 100644 --- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts @@ -285,32 +285,5 @@ export default function (providerContext: FtrProviderContext) { }); }); }); - - describe('deprecated API', () => { - let keyId: string; - before(async () => { - const { body: apiResponse } = await supertest - .post(`/api/fleet/enrollment-api-keys`) - .set('kbn-xsrf', 'xxx') - .send({ - policy_id: 'policy1', - }) - .expect(200); - keyId = apiResponse.item.id; - }); - - it('should get and delete with deprecated API', async () => { - await supertest.get(`/api/fleet/enrollment-api-keys`).set('kbn-xsrf', 'xxx').expect(200); - await supertest - .get(`/api/fleet/enrollment-api-keys/${ENROLLMENT_KEY_ID}`) - .set('kbn-xsrf', 'xxx') - .expect(200); - - await supertest - .delete(`/api/fleet/enrollment-api-keys/${keyId}`) - .set('kbn-xsrf', 'xxx') - .expect(200); - }); - }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts index 474d777fcd906..5afd57dbe1531 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts @@ -144,7 +144,7 @@ export default function (providerContext: FtrProviderContext) { if (_attemptsMade >= attempts) { throw new Error( `Agents not loaded correctly, failing test. All agents: \n: ${JSON.stringify( - apiResponse.list, + apiResponse.items, null, 2 )}` diff --git a/x-pack/test/fleet_api_integration/apis/settings/get.ts b/x-pack/test/fleet_api_integration/apis/settings/get.ts index e035dbc580e72..2a453a40db2cd 100644 --- a/x-pack/test/fleet_api_integration/apis/settings/get.ts +++ b/x-pack/test/fleet_api_integration/apis/settings/get.ts @@ -5,7 +5,6 @@ * 2.0. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -28,29 +27,8 @@ export default function (providerContext: FtrProviderContext) { await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); - it('should respond with fleet_server_hosts', async function () { - // Create a fleet server host - await supertest - .post(`/api/fleet/fleet_server_hosts`) - .set('kbn-xsrf', 'xxxx') - .send({ - id: 'test-default-123', - name: 'Default', - is_default: true, - host_urls: ['https://test.com:8080', 'https://test.com:8081'], - }) - .expect(200); - - // Assert that the hosts appear in the setting response - const response = await supertest - .get(`/api/fleet/settings`) - .set('kbn-xsrf', 'xxxx') - .expect(200); - - expect(response.body.item.fleet_server_hosts).to.eql([ - 'https://test.com:8080', - 'https://test.com:8081', - ]); + it('should respond return settings', async function () { + await supertest.get(`/api/fleet/settings`).set('kbn-xsrf', 'xxxx').expect(200); }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/settings/index.js b/x-pack/test/fleet_api_integration/apis/settings/index.js index e74a278f61ad8..b7a51abf746f7 100644 --- a/x-pack/test/fleet_api_integration/apis/settings/index.js +++ b/x-pack/test/fleet_api_integration/apis/settings/index.js @@ -8,7 +8,6 @@ export default function loadTests({ loadTestFile }) { describe('Settings Endpoints', () => { loadTestFile(require.resolve('./get')); - loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./enrollment')); loadTestFile(require.resolve('./enrollment_privileges')); }); diff --git a/x-pack/test/fleet_api_integration/apis/settings/update.ts b/x-pack/test/fleet_api_integration/apis/settings/update.ts deleted file mode 100644 index b4abd6414c865..0000000000000 --- a/x-pack/test/fleet_api_integration/apis/settings/update.ts +++ /dev/null @@ -1,135 +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 { AGENT_POLICY_INDEX } from '@kbn/fleet-plugin/common'; -import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { skipIfNoDockerRegistry } from '../../helpers'; - -export default function (providerContext: FtrProviderContext) { - const { getService } = providerContext; - const supertest = getService('supertest'); - const kibanaServer = getService('kibanaServer'); - const esClient = getService('es'); - const esArchiver = getService('esArchiver'); - const fleetAndAgents = getService('fleetAndAgents'); - - // Skipped as the Fleet Server hosts settings values are no longer used as of https://github.com/elastic/kibana/issues/137785 - describe.skip('Settings - update', function () { - skipIfNoDockerRegistry(providerContext); - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); - await fleetAndAgents.setup(); - }); - - const createdAgentPolicyIds: string[] = []; - after(async () => { - const deletedPromises = createdAgentPolicyIds.map((agentPolicyId) => - supertest - .post(`/api/fleet/agent_policies/delete`) - .set('kbn-xsrf', 'xxxx') - .send({ agentPolicyId }) - ); - await Promise.all(deletedPromises); - await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); - }); - - it('should explicitly set port on fleet_server_hosts', async function () { - await supertest - .put(`/api/fleet/settings`) - .set('kbn-xsrf', 'xxxx') - .send({ fleet_server_hosts: ['https://test.fr'] }) - .expect(200); - - const { body: getSettingsRes } = await supertest.get(`/api/fleet/settings`).expect(200); - expect(getSettingsRes.item.fleet_server_hosts).to.eql(['https://test.fr:443']); - }); - - it("should bump all agent policy's revision", async function () { - const { body: testPolicy1PostRes } = await supertest - .post(`/api/fleet/agent_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'test 1', - description: '', - namespace: 'default', - }); - createdAgentPolicyIds.push(testPolicy1PostRes.item.id); - - const { body: testPolicy2PostRes } = await supertest - .post(`/api/fleet/agent_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'test2', - description: '', - namespace: 'default', - }); - createdAgentPolicyIds.push(testPolicy2PostRes.item.id); - - await supertest - .put(`/api/fleet/settings`) - .set('kbn-xsrf', 'xxxx') - .send({ fleet_server_hosts: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }) - .expect(200); - - const getTestPolicy1Res = await kibanaServer.savedObjects.get({ - type: 'ingest-agent-policies', - id: testPolicy1PostRes.item.id, - }); - const getTestPolicy2Res = await kibanaServer.savedObjects.get({ - type: 'ingest-agent-policies', - id: testPolicy2PostRes.item.id, - }); - expect(getTestPolicy1Res.attributes.revision).equal(2); - expect(getTestPolicy2Res.attributes.revision).equal(2); - }); - - it('should create agent actions', async function () { - const { body: testPolicyRes } = await supertest - .post(`/api/fleet/agent_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'test', - description: '', - namespace: 'default', - }); - createdAgentPolicyIds.push(testPolicyRes.item.id); - - const beforeRes = await esClient.search({ - index: AGENT_POLICY_INDEX, - ignore_unavailable: true, - body: { - query: { - term: { - policy_id: testPolicyRes.item.id, - }, - }, - }, - }); - - await supertest - .put(`/api/fleet/settings`) - .set('kbn-xsrf', 'xxxx') - .send({ fleet_server_hosts: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }) - .expect(200); - - const res = await esClient.search({ - index: AGENT_POLICY_INDEX, - ignore_unavailable: true, - body: { - query: { - term: { - policy_id: testPolicyRes.item.id, - }, - }, - }, - }); - - expect(res.hits.hits.length).equal(beforeRes.hits.hits.length + 1); - }); - }); -} From 15c1ceb47539f1a8223c049a26abba7373fd8a10 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 6 Nov 2024 08:38:26 -0500 Subject: [PATCH 13/53] [Fleet] Remove deprecated epm APIs (#198434) --- oas_docs/bundle.json | 3830 +++------------- oas_docs/bundle.serverless.json | 3832 +++-------------- oas_docs/output/kibana.serverless.yaml | 2622 ++--------- oas_docs/output/kibana.yaml | 2622 ++--------- .../plugins/fleet/common/constants/routes.ts | 14 +- .../fleet/common/services/route.test.ts | 43 + .../plugins/fleet/common/services/routes.ts | 35 +- .../plugins/fleet/common/types/models/epm.ts | 2 +- .../fleet/common/types/rest_spec/epm.ts | 28 - .../tutorial_module_notice.tsx | 4 +- .../fleet/public/search_provider.test.ts | 14 +- .../plugins/fleet/public/search_provider.ts | 4 +- .../fleet/server/routes/epm/handlers.ts | 17 +- .../fleet/server/routes/epm/index.test.ts | 13 - .../plugins/fleet/server/routes/epm/index.ts | 132 - .../server/services/epm/packages/remove.ts | 11 +- .../services/security/route_required_authz.ts | 2 +- .../fleet/server/types/rest_spec/epm.ts | 73 +- .../public/management/mocks/fleet_mocks.ts | 2 +- .../apis/epm/bulk_get_assets.ts | 7 +- .../apis/epm/install_error_rollback.ts | 4 +- .../fleet_api_integration/apis/epm/setup.ts | 4 +- .../fleet_api_integration/apis/fleet_setup.ts | 2 +- .../apis/package_policy/helper.ts | 13 + .../input_package_create_upgrade.ts | 14 +- .../package_policy/input_package_rollback.ts | 8 +- .../apis/package_policy/update.ts | 8 +- .../apis/package_policy/upgrade.ts | 12 +- .../functional/services/ml/test_resources.ts | 4 +- .../delete_endpoint_fleet_package.ts | 4 +- .../delete_prebuilt_rules_fleet_package.ts | 6 +- 31 files changed, 2502 insertions(+), 10884 deletions(-) create mode 100644 x-pack/plugins/fleet/common/services/route.test.ts create mode 100644 x-pack/test/fleet_api_integration/apis/package_policy/helper.ts diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index d378331392dc0..19094b82be094 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -17554,14 +17554,6 @@ "type": "boolean" } }, - { - "in": "query", - "name": "experimental", - "required": false, - "schema": { - "type": "boolean" - } - }, { "in": "query", "name": "include_policy_templates", @@ -17606,36 +17598,6 @@ "type": "object" }, "type": "array" - }, - "response": { - "items": { - "additionalProperties": false, - "deprecated": true, - "properties": { - "count": { - "type": "number" - }, - "id": { - "type": "string" - }, - "parent_id": { - "type": "string" - }, - "parent_title": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "id", - "title", - "count" - ], - "type": "object" - }, - "type": "array" } }, "required": [ @@ -17844,79 +17806,6 @@ ] }, "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" } }, "required": [ @@ -18119,14 +18008,6 @@ "type": "boolean" } }, - { - "in": "query", - "name": "experimental", - "required": false, - "schema": { - "type": "boolean" - } - }, { "in": "query", "name": "excludeInstallStatus", @@ -18577,7 +18458,6 @@ ], "type": "string" }, - "savedObject": {}, "signature_path": { "type": "string" }, @@ -18619,7 +18499,6 @@ } }, "required": [ - "savedObject", "name", "version", "title", @@ -18628,497 +18507,197 @@ "type": "object" }, "type": "array" + } + }, + "required": [ + "items" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" }, - "response": { + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + }, + "post": { + "description": "Install package by upload", + "operationId": "post-fleet-epm-packages", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "query", + "name": "ignoreMappingUpdateErrors", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + }, + { + "in": "query", + "name": "skipDataStreamRollover", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { + "schema": { + "format": "binary", + "type": "string" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_meta": { + "additionalProperties": false, + "properties": { + "install_source": { + "type": "string" + } + }, + "required": [ + "install_source" + ], + "type": "object" + }, + "items": { "items": { - "additionalProperties": true, - "deprecated": true, - "properties": { - "categories": { - "items": { - "type": "string" - }, - "type": "array" - }, - "conditions": { - "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": false, "properties": { - "elastic": { - "additionalProperties": true, - "properties": { - "capabilities": { - "items": { - "type": "string" - }, - "type": "array" - }, - "subscription": { - "type": "string" - } - }, - "type": "object" + "id": { + "type": "string" }, - "kibana": { - "additionalProperties": true, - "properties": { - "version": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "data_streams": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "discovery": { - "additionalProperties": true, - "properties": { - "fields": { - "items": { - "additionalProperties": true, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" + "originId": { + "type": "string" + }, + "type": { + "enum": [ + "dashboard", + "lens", + "visualization", + "search", + "index-pattern", + "map", + "ml-module", + "security-rule", + "csp-rule-template", + "osquery-pack-asset", + "osquery-saved-query", + "tag" + ], + "type": "string" } }, + "required": [ + "id", + "type" + ], "type": "object" }, - "download": { - "type": "string" - }, - "format_version": { - "type": "string" - }, - "icons": { - "items": { - "additionalProperties": true, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "id": { - "type": "string" - }, - "installationInfo": { - "additionalProperties": true, + { + "additionalProperties": false, "properties": { - "additional_spaces_installed_kibana": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "type": "object" - }, - "created_at": { - "type": "string" - }, - "experimental_data_stream_features": { - "items": { - "additionalProperties": true, - "properties": { - "data_stream": { - "type": "string" - }, - "features": { - "additionalProperties": true, - "properties": { - "doc_value_only_numeric": { - "type": "boolean" - }, - "doc_value_only_other": { - "type": "boolean" - }, - "synthetic_source": { - "type": "boolean" - }, - "tsdb": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "data_stream", - "features" - ], - "type": "object" - }, - "type": "array" + "deferred": { + "type": "boolean" }, - "install_format_schema_version": { + "id": { "type": "string" }, - "install_source": { + "type": { "enum": [ - "registry", - "upload", - "bundled", - "custom" + "index", + "index_template", + "component_template", + "ingest_pipeline", + "ilm_policy", + "data_stream_ilm_policy", + "transform", + "ml_model" ], "type": "string" }, - "install_status": { - "enum": [ - "installed", - "installing", - "install_failed" - ], - "type": "string" - }, - "installed_es": { - "items": { - "additionalProperties": true, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana_space_id": { - "type": "string" - }, - "latest_executed_state": { - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "started_at": { - "type": "string" - } - }, - "required": [ - "name", - "started_at" - ], - "type": "object" - }, - "latest_install_failed_attempts": { - "items": { - "additionalProperties": true, - "properties": { - "created_at": { - "type": "string" - }, - "error": { - "additionalProperties": true, - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object" - }, - "target_version": { - "type": "string" - } - }, - "required": [ - "created_at", - "target_version", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "namespaces": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - }, - "verification_key_id": { - "nullable": true, - "type": "string" - }, - "verification_status": { - "enum": [ - "unverified", - "verified", - "unknown" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "type", - "installed_kibana", - "installed_es", - "name", - "version", - "install_status", - "install_source", - "verification_status" - ], - "type": "object" - }, - "integration": { - "type": "string" - }, - "internal": { - "type": "boolean" - }, - "latestVersion": { - "type": "string" - }, - "name": { - "type": "string" - }, - "owner": { - "additionalProperties": true, - "properties": { - "github": { - "type": "string" - }, - "type": { - "enum": [ - "elastic", - "partner", - "community" - ], - "type": "string" - } - }, - "type": "object" - }, - "path": { - "type": "string" - }, - "policy_templates": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "readme": { - "type": "string" - }, - "release": { - "enum": [ - "ga", - "beta", - "experimental" - ], - "type": "string" - }, - "savedObject": {}, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { + "version": { "type": "string" } }, "required": [ - "license" + "id", + "type" ], "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" } - }, - "required": [ - "savedObject", - "name", - "version", - "title", - "id" - ], - "type": "object" + ] }, "type": "array" } }, "required": [ - "items" + "items", + "_meta" ], "type": "object" } @@ -19127,7 +18706,7 @@ }, "400": { "content": { - "application/json; Elastic-Api-Version=2023-10-31": { + "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { "schema": { "additionalProperties": false, "description": "Generic Error", @@ -19155,10 +18734,12 @@ "tags": [ "Elastic Package Manager (EPM)" ] - }, + } + }, + "/api/fleet/epm/packages/_bulk": { "post": { - "description": "Install package by upload", - "operationId": "post-fleet-epm-packages", + "description": "Bulk install packages", + "operationId": "post-fleet-epm-packages-bulk", "parameters": [ { "description": "The version of the API to use", @@ -19184,278 +18765,16 @@ }, { "in": "query", - "name": "ignoreMappingUpdateErrors", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - }, - { - "in": "query", - "name": "skipDataStreamRollover", + "name": "prerelease", "required": false, "schema": { - "default": false, "type": "boolean" } } ], "requestBody": { "content": { - "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { - "schema": { - "format": "binary", - "type": "string" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "_meta": { - "additionalProperties": false, - "properties": { - "install_source": { - "type": "string" - } - }, - "required": [ - "install_source" - ], - "type": "object" - }, - "items": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - } - }, - "required": [ - "items", - "_meta" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - } - }, - "/api/fleet/epm/packages/_bulk": { - "post": { - "description": "Bulk install packages", - "operationId": "post-fleet-epm-packages-bulk", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { + "application/json; Elastic-Api-Version=2023-10-31": { "schema": { "additionalProperties": false, "properties": { @@ -19653,152 +18972,6 @@ ] }, "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "result": { - "additionalProperties": false, - "properties": { - "assets": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "error": {}, - "installSource": { - "type": "string" - }, - "installType": { - "type": "string" - }, - "status": { - "enum": [ - "installed", - "already_installed" - ], - "type": "string" - } - }, - "required": [ - "error", - "installType" - ], - "type": "object" - }, - "version": { - "type": "string" - } - }, - "required": [ - "name", - "version", - "result" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "error": { - "anyOf": [ - { - "type": "string" - }, - {} - ] - }, - "name": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "name", - "statusCode", - "error" - ], - "type": "object" - } - ] - }, - "type": "array" } }, "required": [ @@ -20102,1641 +19275,106 @@ ], "responses": { "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "type": "string" - }, - "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "items" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - } - }, - "/api/fleet/epm/packages/{pkgName}/stats": { - "get": { - "description": "Get package stats", - "operationId": "get-fleet-epm-packages-pkgname-stats", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "pkgName", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "response": { - "additionalProperties": false, - "properties": { - "agent_policy_count": { - "type": "number" - } - }, - "required": [ - "agent_policy_count" - ], - "type": "object" - } - }, - "required": [ - "response" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - } - }, - "/api/fleet/epm/packages/{pkgName}/{pkgVersion}": { - "delete": { - "description": "Delete package", - "operationId": "delete-fleet-epm-packages-pkgname-pkgversion", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgName", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "pkgVersion", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "force", - "required": false, - "schema": { - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "type": "boolean" - } - }, - "required": [ - "force" - ], - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - } - }, - "required": [ - "items" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - }, - "get": { - "description": "Get package", - "operationId": "get-fleet-epm-packages-pkgname-pkgversion", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "pkgName", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "pkgVersion", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "ignoreUnverified", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "full", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "withMetadata", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - } - ], - "responses": { - "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "item": { - "additionalProperties": true, - "properties": { - "agent": { - "additionalProperties": false, - "properties": { - "privileges": { - "additionalProperties": false, - "properties": { - "root": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "asset_tags": { - "items": { - "additionalProperties": false, - "properties": { - "asset_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "asset_types": { - "items": { - "type": "string" - }, - "type": "array" - }, - "text": { - "type": "string" - } - }, - "required": [ - "text" - ], - "type": "object" - }, - "type": "array" - }, - "assets": { - "additionalProperties": {}, - "type": "object" - }, - "categories": { - "items": { - "type": "string" - }, - "type": "array" - }, - "conditions": { - "additionalProperties": true, - "properties": { - "elastic": { - "additionalProperties": true, - "properties": { - "capabilities": { - "items": { - "type": "string" - }, - "type": "array" - }, - "subscription": { - "type": "string" - } - }, - "type": "object" - }, - "kibana": { - "additionalProperties": true, - "properties": { - "version": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "data_streams": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "discovery": { - "additionalProperties": true, - "properties": { - "fields": { - "items": { - "additionalProperties": true, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - }, - "download": { - "type": "string" - }, - "elasticsearch": { - "additionalProperties": {}, - "type": "object" - }, - "format_version": { - "type": "string" - }, - "icons": { - "items": { - "additionalProperties": true, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "installationInfo": { - "additionalProperties": true, - "properties": { - "additional_spaces_installed_kibana": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "type": "object" - }, - "created_at": { - "type": "string" - }, - "experimental_data_stream_features": { - "items": { - "additionalProperties": true, - "properties": { - "data_stream": { - "type": "string" - }, - "features": { - "additionalProperties": true, - "properties": { - "doc_value_only_numeric": { - "type": "boolean" - }, - "doc_value_only_other": { - "type": "boolean" - }, - "synthetic_source": { - "type": "boolean" - }, - "tsdb": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "data_stream", - "features" - ], - "type": "object" - }, - "type": "array" - }, - "install_format_schema_version": { - "type": "string" - }, - "install_source": { - "enum": [ - "registry", - "upload", - "bundled", - "custom" - ], - "type": "string" - }, - "install_status": { - "enum": [ - "installed", - "installing", - "install_failed" - ], - "type": "string" - }, - "installed_es": { - "items": { - "additionalProperties": true, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana_space_id": { - "type": "string" - }, - "latest_executed_state": { - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "started_at": { - "type": "string" - } - }, - "required": [ - "name", - "started_at" - ], - "type": "object" - }, - "latest_install_failed_attempts": { - "items": { - "additionalProperties": true, - "properties": { - "created_at": { - "type": "string" - }, - "error": { - "additionalProperties": true, - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object" - }, - "target_version": { - "type": "string" - } - }, - "required": [ - "created_at", - "target_version", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "namespaces": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - }, - "verification_key_id": { - "nullable": true, - "type": "string" - }, - "verification_status": { - "enum": [ - "unverified", - "verified", - "unknown" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "type", - "installed_kibana", - "installed_es", - "name", - "version", - "install_status", - "install_source", - "verification_status" - ], - "type": "object" - }, - "internal": { - "type": "boolean" - }, - "keepPoliciesUpToDate": { - "type": "boolean" - }, - "latestVersion": { - "type": "string" - }, - "license": { - "type": "string" - }, - "licensePath": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notice": { - "type": "string" - }, - "owner": { - "additionalProperties": true, - "properties": { - "github": { - "type": "string" - }, - "type": { - "enum": [ - "elastic", - "partner", - "community" - ], - "type": "string" - } - }, - "type": "object" - }, - "path": { - "type": "string" - }, - "policy_templates": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "readme": { - "type": "string" - }, - "release": { - "enum": [ - "ga", - "beta", - "experimental" - ], - "type": "string" - }, - "savedObject": {}, - "screenshots": { - "items": { - "additionalProperties": false, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { - "type": "string" - } - }, - "required": [ - "license" - ], - "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" - } - }, - "required": [ - "savedObject", - "name", - "version", - "title", - "assets" - ], - "type": "object" - }, - "metadata": { - "additionalProperties": false, - "properties": { - "has_policies": { - "type": "boolean" - } - }, - "required": [ - "has_policies" - ], - "type": "object" - }, - "response": { - "additionalProperties": true, - "deprecated": true, - "properties": { - "agent": { - "additionalProperties": false, - "properties": { - "privileges": { - "additionalProperties": false, - "properties": { - "root": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "asset_tags": { - "items": { - "additionalProperties": false, - "properties": { - "asset_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "asset_types": { - "items": { - "type": "string" - }, - "type": "array" - }, - "text": { - "type": "string" - } - }, - "required": [ - "text" - ], - "type": "object" - }, - "type": "array" - }, - "assets": { - "additionalProperties": {}, - "type": "object" - }, - "categories": { - "items": { - "type": "string" - }, - "type": "array" - }, - "conditions": { - "additionalProperties": true, - "properties": { - "elastic": { - "additionalProperties": true, - "properties": { - "capabilities": { - "items": { - "type": "string" - }, - "type": "array" - }, - "subscription": { - "type": "string" - } - }, - "type": "object" - }, - "kibana": { - "additionalProperties": true, - "properties": { - "version": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "data_streams": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "discovery": { - "additionalProperties": true, - "properties": { - "fields": { - "items": { - "additionalProperties": true, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - }, - "download": { - "type": "string" - }, - "elasticsearch": { - "additionalProperties": {}, - "type": "object" - }, - "format_version": { - "type": "string" - }, - "icons": { - "items": { - "additionalProperties": true, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "installationInfo": { - "additionalProperties": true, - "properties": { - "additional_spaces_installed_kibana": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "type": "object" - }, - "created_at": { - "type": "string" - }, - "experimental_data_stream_features": { - "items": { - "additionalProperties": true, - "properties": { - "data_stream": { - "type": "string" - }, - "features": { - "additionalProperties": true, - "properties": { - "doc_value_only_numeric": { - "type": "boolean" - }, - "doc_value_only_other": { - "type": "boolean" - }, - "synthetic_source": { - "type": "boolean" - }, - "tsdb": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "data_stream", - "features" - ], - "type": "object" - }, - "type": "array" - }, - "install_format_schema_version": { - "type": "string" - }, - "install_source": { - "enum": [ - "registry", - "upload", - "bundled", - "custom" - ], - "type": "string" - }, - "install_status": { - "enum": [ - "installed", - "installing", - "install_failed" - ], - "type": "string" - }, - "installed_es": { - "items": { - "additionalProperties": true, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana_space_id": { - "type": "string" - }, - "latest_executed_state": { - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "started_at": { - "type": "string" - } - }, - "required": [ - "name", - "started_at" - ], - "type": "object" - }, - "latest_install_failed_attempts": { - "items": { - "additionalProperties": true, - "properties": { - "created_at": { - "type": "string" - }, - "error": { - "additionalProperties": true, - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object" - }, - "target_version": { - "type": "string" - } - }, - "required": [ - "created_at", - "target_version", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "namespaces": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - }, - "verification_key_id": { - "nullable": true, - "type": "string" - }, - "verification_status": { - "enum": [ - "unverified", - "verified", - "unknown" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "type", - "installed_kibana", - "installed_es", - "name", - "version", - "install_status", - "install_source", - "verification_status" - ], - "type": "object" - }, - "internal": { - "type": "boolean" - }, - "keepPoliciesUpToDate": { - "type": "boolean" - }, - "latestVersion": { - "type": "string" - }, - "license": { - "type": "string" - }, - "licensePath": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notice": { - "type": "string" - }, - "owner": { - "additionalProperties": true, - "properties": { - "github": { - "type": "string" - }, - "type": { - "enum": [ - "elastic", - "partner", - "community" - ], - "type": "string" - } - }, - "type": "object" - }, - "path": { - "type": "string" - }, - "policy_templates": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "readme": { - "type": "string" - }, - "release": { - "enum": [ - "ga", - "beta", - "experimental" - ], - "type": "string" - }, - "savedObject": {}, - "screenshots": { - "items": { - "additionalProperties": false, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { - "type": "string" - } - }, - "required": [ - "license" - ], - "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "items" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + } + }, + "/api/fleet/epm/packages/{pkgName}/stats": { + "get": { + "description": "Get package stats", + "operationId": "get-fleet-epm-packages-pkgname-stats", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "path", + "name": "pkgName", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "response": { + "additionalProperties": false, + "properties": { + "agent_policy_count": { + "type": "number" } }, "required": [ - "savedObject", - "name", - "version", - "title", - "assets" + "agent_policy_count" ], "type": "object" } }, "required": [ - "item" + "response" ], "type": "object" } @@ -21773,10 +19411,12 @@ "tags": [ "Elastic Package Manager (EPM)" ] - }, - "post": { - "description": "Install package from registry", - "operationId": "post-fleet-epm-packages-pkgname-pkgversion", + } + }, + "/api/fleet/epm/packages/{pkgName}/{pkgVersion}": { + "delete": { + "description": "Delete package", + "operationId": "delete-fleet-epm-packages-pkgname-pkgversion", "parameters": [ { "description": "The version of the API to use", @@ -21811,59 +19451,20 @@ { "in": "path", "name": "pkgVersion", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "ignoreMappingUpdateErrors", "required": false, "schema": { - "default": false, - "type": "boolean" + "type": "string" } }, { "in": "query", - "name": "skipDataStreamRollover", + "name": "force", "required": false, "schema": { - "default": false, "type": "boolean" } } ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "default": false, - "type": "boolean" - }, - "ignore_constraints": { - "default": false, - "type": "boolean" - } - }, - "type": "object" - } - } - } - }, "responses": { "200": { "content": { @@ -21871,18 +19472,6 @@ "schema": { "additionalProperties": false, "properties": { - "_meta": { - "additionalProperties": false, - "properties": { - "install_source": { - "type": "string" - } - }, - "required": [ - "install_source" - ], - "type": "object" - }, "items": { "items": { "anyOf": [ @@ -21954,84 +19543,10 @@ ] }, "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" } }, "required": [ - "items", - "_meta" + "items" ], "type": "object" } @@ -22069,9 +19584,9 @@ "Elastic Package Manager (EPM)" ] }, - "put": { - "description": "Update package settings", - "operationId": "put-fleet-epm-packages-pkgname-pkgversion", + "get": { + "description": "Get package", + "operationId": "get-fleet-epm-packages-pkgname-pkgversion", "parameters": [ { "description": "The version of the API to use", @@ -22080,18 +19595,8 @@ "schema": { "default": "2023-10-31", "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", + "2023-10-31" + ], "type": "string" } }, @@ -22106,30 +19611,45 @@ { "in": "path", "name": "pkgVersion", - "required": true, + "required": false, "schema": { "type": "string" } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "keepPoliciesUpToDate": { - "type": "boolean" - } - }, - "required": [ - "keepPoliciesUpToDate" - ], - "type": "object" - } + }, + { + "in": "query", + "name": "ignoreUnverified", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "prerelease", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "full", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "withMetadata", + "required": false, + "schema": { + "default": false, + "type": "boolean" } } - }, + ], "responses": { "200": { "content": { @@ -22625,90 +20145,429 @@ "experimental" ], "type": "string" - }, - "savedObject": {}, - "screenshots": { - "items": { + }, + "screenshots": { + "items": { + "additionalProperties": false, + "properties": { + "dark_mode": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "size": { + "type": "string" + }, + "src": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "src" + ], + "type": "object" + }, + "type": "array" + }, + "signature_path": { + "type": "string" + }, + "source": { + "additionalProperties": true, + "properties": { + "license": { + "type": "string" + } + }, + "required": [ + "license" + ], + "type": "object" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "enum": [ + "integration", + "input", + "content" + ], + "type": "string" + }, + "vars": { + "items": { + "additionalProperties": {}, + "type": "object" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version", + "title", + "assets" + ], + "type": "object" + }, + "metadata": { + "additionalProperties": false, + "properties": { + "has_policies": { + "type": "boolean" + } + }, + "required": [ + "has_policies" + ], + "type": "object" + } + }, + "required": [ + "item" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + }, + "post": { + "description": "Install package from registry", + "operationId": "post-fleet-epm-packages-pkgname-pkgversion", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "pkgName", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "pkgVersion", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "prerelease", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "ignoreMappingUpdateErrors", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + }, + { + "in": "query", + "name": "skipDataStreamRollover", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "nullable": true, + "properties": { + "force": { + "default": false, + "type": "boolean" + }, + "ignore_constraints": { + "default": false, + "type": "boolean" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_meta": { + "additionalProperties": false, + "properties": { + "install_source": { + "type": "string" + } + }, + "required": [ + "install_source" + ], + "type": "object" + }, + "items": { + "items": { + "anyOf": [ + { "additionalProperties": false, "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { + "id": { "type": "string" }, - "size": { + "originId": { "type": "string" }, - "src": { + "type": { + "enum": [ + "dashboard", + "lens", + "visualization", + "search", + "index-pattern", + "map", + "ml-module", + "security-rule", + "csp-rule-template", + "osquery-pack-asset", + "osquery-saved-query", + "tag" + ], "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "deferred": { + "type": "boolean" }, - "title": { + "id": { "type": "string" }, "type": { + "enum": [ + "index", + "index_template", + "component_template", + "ingest_pipeline", + "ilm_policy", + "data_stream_ilm_policy", + "transform", + "ml_model" + ], + "type": "string" + }, + "version": { "type": "string" } }, "required": [ - "src" + "id", + "type" ], "type": "object" - }, - "type": "array" - }, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { - "type": "string" - } - }, - "required": [ - "license" - ], - "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" - } + } + ] }, - "required": [ - "savedObject", - "name", - "version", - "title", - "assets" - ], - "type": "object" + "type": "array" + } + }, + "required": [ + "items", + "_meta" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" }, - "response": { + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + }, + "put": { + "description": "Update package settings", + "operationId": "put-fleet-epm-packages-pkgname-pkgversion", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "pkgName", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "pkgVersion", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "keepPoliciesUpToDate": { + "type": "boolean" + } + }, + "required": [ + "keepPoliciesUpToDate" + ], + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "item": { "additionalProperties": true, - "deprecated": true, "properties": { "agent": { "additionalProperties": false, @@ -23196,7 +21055,6 @@ ], "type": "string" }, - "savedObject": {}, "screenshots": { "items": { "additionalProperties": false, @@ -23268,7 +21126,6 @@ } }, "required": [ - "savedObject", "name", "version", "title", @@ -23543,265 +21400,6 @@ ] } }, - "/api/fleet/epm/packages/{pkgkey}": { - "delete": { - "operationId": "delete-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "type": "boolean" - } - }, - "required": [ - "force" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - }, - "get": { - "operationId": "get-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "ignoreUnverified", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "full", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "withMetadata", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - }, - "post": { - "operationId": "post-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "ignoreMappingUpdateErrors", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - }, - { - "in": "query", - "name": "skipDataStreamRollover", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "type": "boolean" - } - }, - "required": [ - "force" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - }, - "put": { - "operationId": "put-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "keepPoliciesUpToDate": { - "type": "boolean" - } - }, - "required": [ - "keepPoliciesUpToDate" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - } - }, "/api/fleet/epm/templates/{pkgName}/{pkgVersion}/inputs": { "get": { "description": "Get inputs template", diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 972712045a1c6..bc3d45fe67960 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -17554,14 +17554,6 @@ "type": "boolean" } }, - { - "in": "query", - "name": "experimental", - "required": false, - "schema": { - "type": "boolean" - } - }, { "in": "query", "name": "include_policy_templates", @@ -17606,36 +17598,6 @@ "type": "object" }, "type": "array" - }, - "response": { - "items": { - "additionalProperties": false, - "deprecated": true, - "properties": { - "count": { - "type": "number" - }, - "id": { - "type": "string" - }, - "parent_id": { - "type": "string" - }, - "parent_title": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "id", - "title", - "count" - ], - "type": "object" - }, - "type": "array" } }, "required": [ @@ -17844,79 +17806,6 @@ ] }, "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" } }, "required": [ @@ -18119,14 +18008,6 @@ "type": "boolean" } }, - { - "in": "query", - "name": "experimental", - "required": false, - "schema": { - "type": "boolean" - } - }, { "in": "query", "name": "excludeInstallStatus", @@ -18577,7 +18458,6 @@ ], "type": "string" }, - "savedObject": {}, "signature_path": { "type": "string" }, @@ -18619,7 +18499,6 @@ } }, "required": [ - "savedObject", "name", "version", "title", @@ -18628,497 +18507,197 @@ "type": "object" }, "type": "array" + } + }, + "required": [ + "items" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" }, - "response": { + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + }, + "post": { + "description": "Install package by upload", + "operationId": "post-fleet-epm-packages", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "query", + "name": "ignoreMappingUpdateErrors", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + }, + { + "in": "query", + "name": "skipDataStreamRollover", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { + "schema": { + "format": "binary", + "type": "string" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_meta": { + "additionalProperties": false, + "properties": { + "install_source": { + "type": "string" + } + }, + "required": [ + "install_source" + ], + "type": "object" + }, + "items": { "items": { - "additionalProperties": true, - "deprecated": true, - "properties": { - "categories": { - "items": { - "type": "string" - }, - "type": "array" - }, - "conditions": { - "additionalProperties": true, + "anyOf": [ + { + "additionalProperties": false, "properties": { - "elastic": { - "additionalProperties": true, - "properties": { - "capabilities": { - "items": { - "type": "string" - }, - "type": "array" - }, - "subscription": { - "type": "string" - } - }, - "type": "object" + "id": { + "type": "string" }, - "kibana": { - "additionalProperties": true, - "properties": { - "version": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "data_streams": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "discovery": { - "additionalProperties": true, - "properties": { - "fields": { - "items": { - "additionalProperties": true, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" + "originId": { + "type": "string" + }, + "type": { + "enum": [ + "dashboard", + "lens", + "visualization", + "search", + "index-pattern", + "map", + "ml-module", + "security-rule", + "csp-rule-template", + "osquery-pack-asset", + "osquery-saved-query", + "tag" + ], + "type": "string" } }, + "required": [ + "id", + "type" + ], "type": "object" }, - "download": { - "type": "string" - }, - "format_version": { - "type": "string" - }, - "icons": { - "items": { - "additionalProperties": true, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "id": { - "type": "string" - }, - "installationInfo": { - "additionalProperties": true, + { + "additionalProperties": false, "properties": { - "additional_spaces_installed_kibana": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "type": "object" - }, - "created_at": { - "type": "string" - }, - "experimental_data_stream_features": { - "items": { - "additionalProperties": true, - "properties": { - "data_stream": { - "type": "string" - }, - "features": { - "additionalProperties": true, - "properties": { - "doc_value_only_numeric": { - "type": "boolean" - }, - "doc_value_only_other": { - "type": "boolean" - }, - "synthetic_source": { - "type": "boolean" - }, - "tsdb": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "data_stream", - "features" - ], - "type": "object" - }, - "type": "array" + "deferred": { + "type": "boolean" }, - "install_format_schema_version": { + "id": { "type": "string" }, - "install_source": { + "type": { "enum": [ - "registry", - "upload", - "bundled", - "custom" + "index", + "index_template", + "component_template", + "ingest_pipeline", + "ilm_policy", + "data_stream_ilm_policy", + "transform", + "ml_model" ], "type": "string" }, - "install_status": { - "enum": [ - "installed", - "installing", - "install_failed" - ], - "type": "string" - }, - "installed_es": { - "items": { - "additionalProperties": true, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana_space_id": { - "type": "string" - }, - "latest_executed_state": { - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "started_at": { - "type": "string" - } - }, - "required": [ - "name", - "started_at" - ], - "type": "object" - }, - "latest_install_failed_attempts": { - "items": { - "additionalProperties": true, - "properties": { - "created_at": { - "type": "string" - }, - "error": { - "additionalProperties": true, - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object" - }, - "target_version": { - "type": "string" - } - }, - "required": [ - "created_at", - "target_version", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "namespaces": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - }, - "verification_key_id": { - "nullable": true, - "type": "string" - }, - "verification_status": { - "enum": [ - "unverified", - "verified", - "unknown" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "type", - "installed_kibana", - "installed_es", - "name", - "version", - "install_status", - "install_source", - "verification_status" - ], - "type": "object" - }, - "integration": { - "type": "string" - }, - "internal": { - "type": "boolean" - }, - "latestVersion": { - "type": "string" - }, - "name": { - "type": "string" - }, - "owner": { - "additionalProperties": true, - "properties": { - "github": { - "type": "string" - }, - "type": { - "enum": [ - "elastic", - "partner", - "community" - ], - "type": "string" - } - }, - "type": "object" - }, - "path": { - "type": "string" - }, - "policy_templates": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "readme": { - "type": "string" - }, - "release": { - "enum": [ - "ga", - "beta", - "experimental" - ], - "type": "string" - }, - "savedObject": {}, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { + "version": { "type": "string" } }, "required": [ - "license" + "id", + "type" ], "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" } - }, - "required": [ - "savedObject", - "name", - "version", - "title", - "id" - ], - "type": "object" + ] }, "type": "array" } }, "required": [ - "items" + "items", + "_meta" ], "type": "object" } @@ -19127,7 +18706,7 @@ }, "400": { "content": { - "application/json; Elastic-Api-Version=2023-10-31": { + "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { "schema": { "additionalProperties": false, "description": "Generic Error", @@ -19155,10 +18734,12 @@ "tags": [ "Elastic Package Manager (EPM)" ] - }, + } + }, + "/api/fleet/epm/packages/_bulk": { "post": { - "description": "Install package by upload", - "operationId": "post-fleet-epm-packages", + "description": "Bulk install packages", + "operationId": "post-fleet-epm-packages-bulk", "parameters": [ { "description": "The version of the API to use", @@ -19184,278 +18765,16 @@ }, { "in": "query", - "name": "ignoreMappingUpdateErrors", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - }, - { - "in": "query", - "name": "skipDataStreamRollover", + "name": "prerelease", "required": false, "schema": { - "default": false, "type": "boolean" } } ], "requestBody": { "content": { - "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { - "schema": { - "format": "binary", - "type": "string" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "_meta": { - "additionalProperties": false, - "properties": { - "install_source": { - "type": "string" - } - }, - "required": [ - "install_source" - ], - "type": "object" - }, - "items": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - } - }, - "required": [ - "items", - "_meta" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/gzip; application/zip; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - } - }, - "/api/fleet/epm/packages/_bulk": { - "post": { - "description": "Bulk install packages", - "operationId": "post-fleet-epm-packages-bulk", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { + "application/json; Elastic-Api-Version=2023-10-31": { "schema": { "additionalProperties": false, "properties": { @@ -19653,152 +18972,6 @@ ] }, "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "result": { - "additionalProperties": false, - "properties": { - "assets": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "error": {}, - "installSource": { - "type": "string" - }, - "installType": { - "type": "string" - }, - "status": { - "enum": [ - "installed", - "already_installed" - ], - "type": "string" - } - }, - "required": [ - "error", - "installType" - ], - "type": "object" - }, - "version": { - "type": "string" - } - }, - "required": [ - "name", - "version", - "result" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "error": { - "anyOf": [ - { - "type": "string" - }, - {} - ] - }, - "name": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "name", - "statusCode", - "error" - ], - "type": "object" - } - ] - }, - "type": "array" } }, "required": [ @@ -20104,1639 +19277,104 @@ "200": { "content": { "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "type": "string" - }, - "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "items" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - } - }, - "/api/fleet/epm/packages/{pkgName}/stats": { - "get": { - "description": "Get package stats", - "operationId": "get-fleet-epm-packages-pkgname-stats", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "pkgName", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "response": { - "additionalProperties": false, - "properties": { - "agent_policy_count": { - "type": "number" - } - }, - "required": [ - "agent_policy_count" - ], - "type": "object" - } - }, - "required": [ - "response" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - } - }, - "/api/fleet/epm/packages/{pkgName}/{pkgVersion}": { - "delete": { - "description": "Delete package", - "operationId": "delete-fleet-epm-packages-pkgname-pkgversion", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgName", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "pkgVersion", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "force", - "required": false, - "schema": { - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "type": "boolean" - } - }, - "required": [ - "force" - ], - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "items": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - } - }, - "required": [ - "items" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message" - ], - "type": "object" - } - } - } - } - }, - "summary": "", - "tags": [ - "Elastic Package Manager (EPM)" - ] - }, - "get": { - "description": "Get package", - "operationId": "get-fleet-epm-packages-pkgname-pkgversion", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "pkgName", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "pkgVersion", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "ignoreUnverified", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "full", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "withMetadata", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - } - ], - "responses": { - "200": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "item": { - "additionalProperties": true, - "properties": { - "agent": { - "additionalProperties": false, - "properties": { - "privileges": { - "additionalProperties": false, - "properties": { - "root": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "asset_tags": { - "items": { - "additionalProperties": false, - "properties": { - "asset_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "asset_types": { - "items": { - "type": "string" - }, - "type": "array" - }, - "text": { - "type": "string" - } - }, - "required": [ - "text" - ], - "type": "object" - }, - "type": "array" - }, - "assets": { - "additionalProperties": {}, - "type": "object" - }, - "categories": { - "items": { - "type": "string" - }, - "type": "array" - }, - "conditions": { - "additionalProperties": true, - "properties": { - "elastic": { - "additionalProperties": true, - "properties": { - "capabilities": { - "items": { - "type": "string" - }, - "type": "array" - }, - "subscription": { - "type": "string" - } - }, - "type": "object" - }, - "kibana": { - "additionalProperties": true, - "properties": { - "version": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "data_streams": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "discovery": { - "additionalProperties": true, - "properties": { - "fields": { - "items": { - "additionalProperties": true, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - }, - "download": { - "type": "string" - }, - "elasticsearch": { - "additionalProperties": {}, - "type": "object" - }, - "format_version": { - "type": "string" - }, - "icons": { - "items": { - "additionalProperties": true, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "installationInfo": { - "additionalProperties": true, - "properties": { - "additional_spaces_installed_kibana": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "type": "object" - }, - "created_at": { - "type": "string" - }, - "experimental_data_stream_features": { - "items": { - "additionalProperties": true, - "properties": { - "data_stream": { - "type": "string" - }, - "features": { - "additionalProperties": true, - "properties": { - "doc_value_only_numeric": { - "type": "boolean" - }, - "doc_value_only_other": { - "type": "boolean" - }, - "synthetic_source": { - "type": "boolean" - }, - "tsdb": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "data_stream", - "features" - ], - "type": "object" - }, - "type": "array" - }, - "install_format_schema_version": { - "type": "string" - }, - "install_source": { - "enum": [ - "registry", - "upload", - "bundled", - "custom" - ], - "type": "string" - }, - "install_status": { - "enum": [ - "installed", - "installing", - "install_failed" - ], - "type": "string" - }, - "installed_es": { - "items": { - "additionalProperties": true, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana_space_id": { - "type": "string" - }, - "latest_executed_state": { - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "started_at": { - "type": "string" - } - }, - "required": [ - "name", - "started_at" - ], - "type": "object" - }, - "latest_install_failed_attempts": { - "items": { - "additionalProperties": true, - "properties": { - "created_at": { - "type": "string" - }, - "error": { - "additionalProperties": true, - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object" - }, - "target_version": { - "type": "string" - } - }, - "required": [ - "created_at", - "target_version", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "namespaces": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - }, - "verification_key_id": { - "nullable": true, - "type": "string" - }, - "verification_status": { - "enum": [ - "unverified", - "verified", - "unknown" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "type", - "installed_kibana", - "installed_es", - "name", - "version", - "install_status", - "install_source", - "verification_status" - ], - "type": "object" - }, - "internal": { - "type": "boolean" - }, - "keepPoliciesUpToDate": { - "type": "boolean" - }, - "latestVersion": { - "type": "string" - }, - "license": { - "type": "string" - }, - "licensePath": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notice": { - "type": "string" - }, - "owner": { - "additionalProperties": true, - "properties": { - "github": { - "type": "string" - }, - "type": { - "enum": [ - "elastic", - "partner", - "community" - ], - "type": "string" - } - }, - "type": "object" - }, - "path": { - "type": "string" - }, - "policy_templates": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "readme": { - "type": "string" - }, - "release": { - "enum": [ - "ga", - "beta", - "experimental" - ], - "type": "string" - }, - "savedObject": {}, - "screenshots": { - "items": { - "additionalProperties": false, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { - "type": "string" - } - }, - "required": [ - "license" - ], - "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" - } - }, - "required": [ - "savedObject", - "name", - "version", - "title", - "assets" - ], - "type": "object" - }, - "metadata": { - "additionalProperties": false, - "properties": { - "has_policies": { - "type": "boolean" - } - }, - "required": [ - "has_policies" - ], - "type": "object" - }, - "response": { - "additionalProperties": true, - "deprecated": true, - "properties": { - "agent": { - "additionalProperties": false, - "properties": { - "privileges": { - "additionalProperties": false, - "properties": { - "root": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "asset_tags": { - "items": { - "additionalProperties": false, - "properties": { - "asset_ids": { - "items": { - "type": "string" - }, - "type": "array" - }, - "asset_types": { - "items": { - "type": "string" - }, - "type": "array" - }, - "text": { - "type": "string" - } - }, - "required": [ - "text" - ], - "type": "object" - }, - "type": "array" - }, - "assets": { - "additionalProperties": {}, - "type": "object" - }, - "categories": { - "items": { - "type": "string" - }, - "type": "array" - }, - "conditions": { - "additionalProperties": true, - "properties": { - "elastic": { - "additionalProperties": true, - "properties": { - "capabilities": { - "items": { - "type": "string" - }, - "type": "array" - }, - "subscription": { - "type": "string" - } - }, - "type": "object" - }, - "kibana": { - "additionalProperties": true, - "properties": { - "version": { - "type": "string" - } - }, - "type": "object" - } - }, - "type": "object" - }, - "data_streams": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "description": { - "type": "string" - }, - "discovery": { - "additionalProperties": true, - "properties": { - "fields": { - "items": { - "additionalProperties": true, - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object" - }, - "type": "array" - } - }, - "type": "object" - }, - "download": { - "type": "string" - }, - "elasticsearch": { - "additionalProperties": {}, - "type": "object" - }, - "format_version": { - "type": "string" - }, - "icons": { - "items": { - "additionalProperties": true, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "installationInfo": { - "additionalProperties": true, - "properties": { - "additional_spaces_installed_kibana": { - "additionalProperties": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "type": "object" - }, - "created_at": { - "type": "string" - }, - "experimental_data_stream_features": { - "items": { - "additionalProperties": true, - "properties": { - "data_stream": { - "type": "string" - }, - "features": { - "additionalProperties": true, - "properties": { - "doc_value_only_numeric": { - "type": "boolean" - }, - "doc_value_only_other": { - "type": "boolean" - }, - "synthetic_source": { - "type": "boolean" - }, - "tsdb": { - "type": "boolean" - } - }, - "type": "object" - } - }, - "required": [ - "data_stream", - "features" - ], - "type": "object" - }, - "type": "array" - }, - "install_format_schema_version": { - "type": "string" - }, - "install_source": { - "enum": [ - "registry", - "upload", - "bundled", - "custom" - ], - "type": "string" - }, - "install_status": { - "enum": [ - "installed", - "installing", - "install_failed" - ], - "type": "string" - }, - "installed_es": { - "items": { - "additionalProperties": true, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana": { - "items": { - "additionalProperties": true, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - "type": "array" - }, - "installed_kibana_space_id": { - "type": "string" - }, - "latest_executed_state": { - "additionalProperties": true, - "properties": { - "error": { - "type": "string" - }, - "name": { - "type": "string" - }, - "started_at": { - "type": "string" - } - }, - "required": [ - "name", - "started_at" - ], - "type": "object" - }, - "latest_install_failed_attempts": { - "items": { - "additionalProperties": true, - "properties": { - "created_at": { - "type": "string" - }, - "error": { - "additionalProperties": true, - "properties": { - "message": { - "type": "string" - }, - "name": { - "type": "string" - }, - "stack": { - "type": "string" - } - }, - "required": [ - "name", - "message" - ], - "type": "object" - }, - "target_version": { - "type": "string" - } - }, - "required": [ - "created_at", - "target_version", - "error" - ], - "type": "object" - }, - "type": "array" - }, - "name": { - "type": "string" - }, - "namespaces": { - "items": { - "type": "string" - }, - "type": "array" - }, - "type": { - "type": "string" - }, - "updated_at": { - "type": "string" - }, - "verification_key_id": { - "nullable": true, - "type": "string" - }, - "verification_status": { - "enum": [ - "unverified", - "verified", - "unknown" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "type", - "installed_kibana", - "installed_es", - "name", - "version", - "install_status", - "install_source", - "verification_status" - ], - "type": "object" - }, - "internal": { - "type": "boolean" - }, - "keepPoliciesUpToDate": { - "type": "boolean" - }, - "latestVersion": { - "type": "string" - }, - "license": { - "type": "string" - }, - "licensePath": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notice": { - "type": "string" - }, - "owner": { - "additionalProperties": true, - "properties": { - "github": { - "type": "string" - }, - "type": { - "enum": [ - "elastic", - "partner", - "community" - ], - "type": "string" - } - }, - "type": "object" - }, - "path": { - "type": "string" - }, - "policy_templates": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "readme": { - "type": "string" - }, - "release": { - "enum": [ - "ga", - "beta", - "experimental" - ], - "type": "string" - }, - "savedObject": {}, - "screenshots": { - "items": { - "additionalProperties": false, - "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size": { - "type": "string" - }, - "src": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "src" - ], - "type": "object" - }, - "type": "array" - }, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { - "type": "string" - } - }, - "required": [ - "license" - ], - "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" + "schema": { + "additionalProperties": false, + "properties": { + "items": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "items" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + } + }, + "/api/fleet/epm/packages/{pkgName}/stats": { + "get": { + "description": "Get package stats", + "operationId": "get-fleet-epm-packages-pkgname-stats", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "in": "path", + "name": "pkgName", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "response": { + "additionalProperties": false, + "properties": { + "agent_policy_count": { + "type": "number" } }, "required": [ - "savedObject", - "name", - "version", - "title", - "assets" + "agent_policy_count" ], "type": "object" } }, "required": [ - "item" + "response" ], "type": "object" } @@ -21773,10 +19411,12 @@ "tags": [ "Elastic Package Manager (EPM)" ] - }, - "post": { - "description": "Install package from registry", - "operationId": "post-fleet-epm-packages-pkgname-pkgversion", + } + }, + "/api/fleet/epm/packages/{pkgName}/{pkgVersion}": { + "delete": { + "description": "Delete package", + "operationId": "delete-fleet-epm-packages-pkgname-pkgversion", "parameters": [ { "description": "The version of the API to use", @@ -21811,59 +19451,20 @@ { "in": "path", "name": "pkgVersion", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "ignoreMappingUpdateErrors", "required": false, "schema": { - "default": false, - "type": "boolean" + "type": "string" } }, { "in": "query", - "name": "skipDataStreamRollover", + "name": "force", "required": false, "schema": { - "default": false, "type": "boolean" } } ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "default": false, - "type": "boolean" - }, - "ignore_constraints": { - "default": false, - "type": "boolean" - } - }, - "type": "object" - } - } - } - }, "responses": { "200": { "content": { @@ -21871,18 +19472,6 @@ "schema": { "additionalProperties": false, "properties": { - "_meta": { - "additionalProperties": false, - "properties": { - "install_source": { - "type": "string" - } - }, - "required": [ - "install_source" - ], - "type": "object" - }, "items": { "items": { "anyOf": [ @@ -21954,84 +19543,10 @@ ] }, "type": "array" - }, - "response": { - "deprecated": true, - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "originId": { - "type": "string" - }, - "type": { - "enum": [ - "dashboard", - "lens", - "visualization", - "search", - "index-pattern", - "map", - "ml-module", - "security-rule", - "csp-rule-template", - "osquery-pack-asset", - "osquery-saved-query", - "tag" - ], - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "deferred": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "type": { - "enum": [ - "index", - "index_template", - "component_template", - "ingest_pipeline", - "ilm_policy", - "data_stream_ilm_policy", - "transform", - "ml_model" - ], - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" } }, "required": [ - "items", - "_meta" + "items" ], "type": "object" } @@ -22069,29 +19584,19 @@ "Elastic Package Manager (EPM)" ] }, - "put": { - "description": "Update package settings", - "operationId": "put-fleet-epm-packages-pkgname-pkgversion", + "get": { + "description": "Get package", + "operationId": "get-fleet-epm-packages-pkgname-pkgversion", "parameters": [ { "description": "The version of the API to use", "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, + "name": "elastic-api-version", "schema": { - "example": "true", + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], "type": "string" } }, @@ -22106,30 +19611,45 @@ { "in": "path", "name": "pkgVersion", - "required": true, + "required": false, "schema": { "type": "string" } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "keepPoliciesUpToDate": { - "type": "boolean" - } - }, - "required": [ - "keepPoliciesUpToDate" - ], - "type": "object" - } + }, + { + "in": "query", + "name": "ignoreUnverified", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "prerelease", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "full", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "withMetadata", + "required": false, + "schema": { + "default": false, + "type": "boolean" } } - }, + ], "responses": { "200": { "content": { @@ -22625,90 +20145,429 @@ "experimental" ], "type": "string" - }, - "savedObject": {}, - "screenshots": { - "items": { + }, + "screenshots": { + "items": { + "additionalProperties": false, + "properties": { + "dark_mode": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "size": { + "type": "string" + }, + "src": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "src" + ], + "type": "object" + }, + "type": "array" + }, + "signature_path": { + "type": "string" + }, + "source": { + "additionalProperties": true, + "properties": { + "license": { + "type": "string" + } + }, + "required": [ + "license" + ], + "type": "object" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "enum": [ + "integration", + "input", + "content" + ], + "type": "string" + }, + "vars": { + "items": { + "additionalProperties": {}, + "type": "object" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "required": [ + "name", + "version", + "title", + "assets" + ], + "type": "object" + }, + "metadata": { + "additionalProperties": false, + "properties": { + "has_policies": { + "type": "boolean" + } + }, + "required": [ + "has_policies" + ], + "type": "object" + } + }, + "required": [ + "item" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + }, + "post": { + "description": "Install package from registry", + "operationId": "post-fleet-epm-packages-pkgname-pkgversion", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "pkgName", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "pkgVersion", + "required": false, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "prerelease", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "ignoreMappingUpdateErrors", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + }, + { + "in": "query", + "name": "skipDataStreamRollover", + "required": false, + "schema": { + "default": false, + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "nullable": true, + "properties": { + "force": { + "default": false, + "type": "boolean" + }, + "ignore_constraints": { + "default": false, + "type": "boolean" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "_meta": { + "additionalProperties": false, + "properties": { + "install_source": { + "type": "string" + } + }, + "required": [ + "install_source" + ], + "type": "object" + }, + "items": { + "items": { + "anyOf": [ + { "additionalProperties": false, "properties": { - "dark_mode": { - "type": "boolean" - }, - "path": { + "id": { "type": "string" }, - "size": { + "originId": { "type": "string" }, - "src": { + "type": { + "enum": [ + "dashboard", + "lens", + "visualization", + "search", + "index-pattern", + "map", + "ml-module", + "security-rule", + "csp-rule-template", + "osquery-pack-asset", + "osquery-saved-query", + "tag" + ], "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "deferred": { + "type": "boolean" }, - "title": { + "id": { "type": "string" }, "type": { + "enum": [ + "index", + "index_template", + "component_template", + "ingest_pipeline", + "ilm_policy", + "data_stream_ilm_policy", + "transform", + "ml_model" + ], + "type": "string" + }, + "version": { "type": "string" } }, "required": [ - "src" + "id", + "type" ], "type": "object" - }, - "type": "array" - }, - "signature_path": { - "type": "string" - }, - "source": { - "additionalProperties": true, - "properties": { - "license": { - "type": "string" - } - }, - "required": [ - "license" - ], - "type": "object" - }, - "status": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "enum": [ - "integration", - "input", - "content" - ], - "type": "string" - }, - "vars": { - "items": { - "additionalProperties": {}, - "type": "object" - }, - "type": "array" - }, - "version": { - "type": "string" - } + } + ] }, - "required": [ - "savedObject", - "name", - "version", - "title", - "assets" - ], - "type": "object" + "type": "array" + } + }, + "required": [ + "items", + "_meta" + ], + "type": "object" + } + } + } + }, + "400": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "description": "Generic Error", + "properties": { + "error": { + "type": "string" }, - "response": { + "message": { + "type": "string" + }, + "statusCode": { + "type": "number" + } + }, + "required": [ + "message" + ], + "type": "object" + } + } + } + } + }, + "summary": "", + "tags": [ + "Elastic Package Manager (EPM)" + ] + }, + "put": { + "description": "Update package settings", + "operationId": "put-fleet-epm-packages-pkgname-pkgversion", + "parameters": [ + { + "description": "The version of the API to use", + "in": "header", + "name": "elastic-api-version", + "schema": { + "default": "2023-10-31", + "enum": [ + "2023-10-31" + ], + "type": "string" + } + }, + { + "description": "A required header to protect against CSRF attacks", + "in": "header", + "name": "kbn-xsrf", + "required": true, + "schema": { + "example": "true", + "type": "string" + } + }, + { + "in": "path", + "name": "pkgName", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "pkgVersion", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "keepPoliciesUpToDate": { + "type": "boolean" + } + }, + "required": [ + "keepPoliciesUpToDate" + ], + "type": "object" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json; Elastic-Api-Version=2023-10-31": { + "schema": { + "additionalProperties": false, + "properties": { + "item": { "additionalProperties": true, - "deprecated": true, "properties": { "agent": { "additionalProperties": false, @@ -23196,7 +21055,6 @@ ], "type": "string" }, - "savedObject": {}, "screenshots": { "items": { "additionalProperties": false, @@ -23268,7 +21126,6 @@ } }, "required": [ - "savedObject", "name", "version", "title", @@ -23543,265 +21400,6 @@ ] } }, - "/api/fleet/epm/packages/{pkgkey}": { - "delete": { - "operationId": "delete-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "type": "boolean" - } - }, - "required": [ - "force" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - }, - "get": { - "operationId": "get-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "ignoreUnverified", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "full", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "withMetadata", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - } - ], - "responses": {}, - "summary": "", - "tags": [] - }, - "post": { - "operationId": "post-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "prerelease", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "in": "query", - "name": "ignoreMappingUpdateErrors", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - }, - { - "in": "query", - "name": "skipDataStreamRollover", - "required": false, - "schema": { - "default": false, - "type": "boolean" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "nullable": true, - "properties": { - "force": { - "type": "boolean" - } - }, - "required": [ - "force" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - }, - "put": { - "operationId": "put-fleet-epm-packages-pkgkey", - "parameters": [ - { - "description": "The version of the API to use", - "in": "header", - "name": "elastic-api-version", - "schema": { - "default": "2023-10-31", - "enum": [ - "2023-10-31" - ], - "type": "string" - } - }, - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - }, - { - "in": "path", - "name": "pkgkey", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json; Elastic-Api-Version=2023-10-31": { - "schema": { - "additionalProperties": false, - "properties": { - "keepPoliciesUpToDate": { - "type": "boolean" - } - }, - "required": [ - "keepPoliciesUpToDate" - ], - "type": "object" - } - } - } - }, - "responses": {}, - "summary": "", - "tags": [] - } - }, "/api/fleet/epm/templates/{pkgName}/{pkgVersion}/inputs": { "get": { "description": "Get inputs template", diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index a1cc60cbe7bd0..78c8541059d26 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -18230,11 +18230,6 @@ paths: required: false schema: type: boolean - - in: query - name: experimental - required: false - schema: - type: boolean - in: query name: include_policy_templates required: false @@ -18268,27 +18263,6 @@ paths: - title - count type: array - response: - items: - additionalProperties: false - deprecated: true - type: object - properties: - count: - type: number - id: - type: string - parent_id: - type: string - parent_title: - type: string - title: - type: string - required: - - id - - title - - count - type: array required: - items '400': @@ -18431,59 +18405,6 @@ paths: - id - type type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array required: - items - _meta @@ -18612,11 +18533,6 @@ paths: required: false schema: type: boolean - - in: query - name: experimental - required: false - schema: - type: boolean - in: query name: excludeInstallStatus required: false @@ -18941,7 +18857,6 @@ paths: - beta - experimental type: string - savedObject: {} signature_path: type: string source: @@ -18970,536 +18885,135 @@ paths: version: type: string required: - - savedObject - name - version - title - id type: array - response: + required: + - items + '400': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + description: Generic Error + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + required: + - message + summary: '' + tags: + - Elastic Package Manager (EPM) + post: + description: Install package by upload + operationId: post-fleet-epm-packages + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: query + name: ignoreMappingUpdateErrors + required: false + schema: + default: false + type: boolean + - in: query + name: skipDataStreamRollover + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/gzip; application/zip; Elastic-Api-Version=2023-10-31: + schema: + format: binary + type: string + responses: + '200': + content: + application/gzip; application/zip; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _meta: + additionalProperties: false + type: object + properties: + install_source: + type: string + required: + - install_source + items: items: - additionalProperties: true - deprecated: true - type: object - properties: - categories: - items: - type: string - type: array - conditions: - additionalProperties: true + anyOf: + - additionalProperties: false type: object properties: - elastic: - additionalProperties: true - type: object - properties: - capabilities: - items: - type: string - type: array - subscription: - type: string - kibana: - additionalProperties: true - type: object - properties: - version: - type: string - data_streams: - items: - additionalProperties: {} - type: object - type: array - description: - type: string - discovery: - additionalProperties: true + id: + type: string + originId: + type: string + type: + enum: + - dashboard + - lens + - visualization + - search + - index-pattern + - map + - ml-module + - security-rule + - csp-rule-template + - osquery-pack-asset + - osquery-saved-query + - tag + type: string + required: + - id + - type + - additionalProperties: false type: object properties: - fields: - items: - additionalProperties: true - type: object - properties: - name: - type: string - required: - - name - type: array - download: - type: string - format_version: - type: string - icons: - items: - additionalProperties: true - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - id: - type: string - installationInfo: - additionalProperties: true - type: object - properties: - additional_spaces_installed_kibana: - additionalProperties: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - type: object - created_at: - type: string - experimental_data_stream_features: - items: - additionalProperties: true - type: object - properties: - data_stream: - type: string - features: - additionalProperties: true - type: object - properties: - doc_value_only_numeric: - type: boolean - doc_value_only_other: - type: boolean - synthetic_source: - type: boolean - tsdb: - type: boolean - required: - - data_stream - - features - type: array - install_format_schema_version: - type: string - install_source: - enum: - - registry - - upload - - bundled - - custom - type: string - install_status: - enum: - - installed - - installing - - install_failed - type: string - installed_es: - items: - additionalProperties: true - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - installed_kibana: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - installed_kibana_space_id: - type: string - latest_executed_state: - additionalProperties: true - type: object - properties: - error: - type: string - name: - type: string - started_at: - type: string - required: - - name - - started_at - latest_install_failed_attempts: - items: - additionalProperties: true - type: object - properties: - created_at: - type: string - error: - additionalProperties: true - type: object - properties: - message: - type: string - name: - type: string - stack: - type: string - required: - - name - - message - target_version: - type: string - required: - - created_at - - target_version - - error - type: array - name: + deferred: + type: boolean + id: type: string - namespaces: - items: - type: string - type: array type: - type: string - updated_at: - type: string - verification_key_id: - nullable: true - type: string - verification_status: enum: - - unverified - - verified - - unknown + - index + - index_template + - component_template + - ingest_pipeline + - ilm_policy + - data_stream_ilm_policy + - transform + - ml_model type: string version: type: string required: - - type - - installed_kibana - - installed_es - - name - - version - - install_status - - install_source - - verification_status - integration: - type: string - internal: - type: boolean - latestVersion: - type: string - name: - type: string - owner: - additionalProperties: true - type: object - properties: - github: - type: string - type: - enum: - - elastic - - partner - - community - type: string - path: - type: string - policy_templates: - items: - additionalProperties: {} - type: object - type: array - readme: - type: string - release: - enum: - - ga - - beta - - experimental - type: string - savedObject: {} - signature_path: - type: string - source: - additionalProperties: true - type: object - properties: - license: - type: string - required: - - license - status: - type: string - title: - type: string - type: - enum: - - integration - - input - - content - type: string - vars: - items: - additionalProperties: {} - type: object - type: array - version: - type: string - required: - - savedObject - - name - - version - - title - - id - type: array - required: - - items - '400': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - required: - - message - summary: '' - tags: - - Elastic Package Manager (EPM) - post: - description: Install package by upload - operationId: post-fleet-epm-packages - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: query - name: ignoreMappingUpdateErrors - required: false - schema: - default: false - type: boolean - - in: query - name: skipDataStreamRollover - required: false - schema: - default: false - type: boolean - requestBody: - content: - application/gzip; application/zip; Elastic-Api-Version=2023-10-31: - schema: - format: binary - type: string - responses: - '200': - content: - application/gzip; application/zip; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - _meta: - additionalProperties: false - type: object - properties: - install_source: - type: string - required: - - install_source - items: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id + - id - type type: array required: @@ -19589,1442 +19103,105 @@ paths: properties: items: items: - anyOf: - - additionalProperties: false - type: object - properties: - name: - type: string - result: - additionalProperties: false - type: object - properties: - assets: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - error: {} - installSource: - type: string - installType: - type: string - status: - enum: - - installed - - already_installed - type: string - required: - - error - - installType - version: - type: string - required: - - name - - version - - result - - additionalProperties: false - type: object - properties: - error: - anyOf: - - type: string - - {} - name: - type: string - statusCode: - type: number - required: - - name - - statusCode - - error - type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - name: - type: string - result: - additionalProperties: false - type: object - properties: - assets: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - error: {} - installSource: - type: string - installType: - type: string - status: - enum: - - installed - - already_installed - type: string - required: - - error - - installType - version: - type: string - required: - - name - - version - - result - - additionalProperties: false - type: object - properties: - error: - anyOf: - - type: string - - {} - name: - type: string - statusCode: - type: number - required: - - name - - statusCode - - error - type: array - required: - - items - '400': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - required: - - message - summary: '' - tags: - - Elastic Package Manager (EPM) - /api/fleet/epm/packages/{pkgkey}: - delete: - operationId: delete-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - type: boolean - required: - - force - responses: {} - summary: '' - tags: [] - get: - operationId: get-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - - in: query - name: ignoreUnverified - required: false - schema: - type: boolean - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: full - required: false - schema: - type: boolean - - in: query - name: withMetadata - required: false - schema: - default: false - type: boolean - responses: {} - summary: '' - tags: [] - post: - operationId: post-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: ignoreMappingUpdateErrors - required: false - schema: - default: false - type: boolean - - in: query - name: skipDataStreamRollover - required: false - schema: - default: false - type: boolean - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - type: boolean - required: - - force - responses: {} - summary: '' - tags: [] - put: - operationId: put-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - keepPoliciesUpToDate: - type: boolean - required: - - keepPoliciesUpToDate - responses: {} - summary: '' - tags: [] - /api/fleet/epm/packages/{pkgName}/{pkgVersion}: - delete: - description: Delete package - operationId: delete-fleet-epm-packages-pkgname-pkgversion - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgName - required: true - schema: - type: string - - in: path - name: pkgVersion - required: true - schema: - type: string - - in: query - name: force - required: false - schema: - type: boolean - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - type: boolean - required: - - force - responses: - '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - items: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - required: - - items - '400': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - required: - - message - summary: '' - tags: - - Elastic Package Manager (EPM) - get: - description: Get package - operationId: get-fleet-epm-packages-pkgname-pkgversion - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: path - name: pkgName - required: true - schema: - type: string - - in: path - name: pkgVersion - required: true - schema: - type: string - - in: query - name: ignoreUnverified - required: false - schema: - type: boolean - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: full - required: false - schema: - type: boolean - - in: query - name: withMetadata - required: false - schema: - default: false - type: boolean - responses: - '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - item: - additionalProperties: true - type: object - properties: - agent: - additionalProperties: false - type: object - properties: - privileges: - additionalProperties: false - type: object - properties: - root: - type: boolean - asset_tags: - items: - additionalProperties: false - type: object - properties: - asset_ids: - items: - type: string - type: array - asset_types: - items: - type: string - type: array - text: - type: string - required: - - text - type: array - assets: - additionalProperties: {} - type: object - categories: - items: - type: string - type: array - conditions: - additionalProperties: true - type: object - properties: - elastic: - additionalProperties: true - type: object - properties: - capabilities: - items: - type: string - type: array - subscription: - type: string - kibana: - additionalProperties: true - type: object - properties: - version: - type: string - data_streams: - items: - additionalProperties: {} - type: object - type: array - description: - type: string - discovery: - additionalProperties: true - type: object - properties: - fields: - items: - additionalProperties: true - type: object - properties: - name: - type: string - required: - - name - type: array - download: - type: string - elasticsearch: - additionalProperties: {} - type: object - format_version: - type: string - icons: - items: - additionalProperties: true - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - installationInfo: - additionalProperties: true - type: object - properties: - additional_spaces_installed_kibana: - additionalProperties: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - type: object - created_at: - type: string - experimental_data_stream_features: - items: - additionalProperties: true - type: object - properties: - data_stream: - type: string - features: - additionalProperties: true - type: object - properties: - doc_value_only_numeric: - type: boolean - doc_value_only_other: - type: boolean - synthetic_source: - type: boolean - tsdb: - type: boolean - required: - - data_stream - - features - type: array - install_format_schema_version: - type: string - install_source: - enum: - - registry - - upload - - bundled - - custom - type: string - install_status: - enum: - - installed - - installing - - install_failed - type: string - installed_es: - items: - additionalProperties: true - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - installed_kibana: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - installed_kibana_space_id: - type: string - latest_executed_state: - additionalProperties: true - type: object - properties: - error: - type: string - name: - type: string - started_at: - type: string - required: - - name - - started_at - latest_install_failed_attempts: - items: - additionalProperties: true - type: object - properties: - created_at: - type: string - error: - additionalProperties: true - type: object - properties: - message: - type: string - name: - type: string - stack: - type: string - required: - - name - - message - target_version: - type: string - required: - - created_at - - target_version - - error - type: array - name: - type: string - namespaces: - items: - type: string - type: array - type: - type: string - updated_at: - type: string - verification_key_id: - nullable: true - type: string - verification_status: - enum: - - unverified - - verified - - unknown - type: string - version: - type: string - required: - - type - - installed_kibana - - installed_es - - name - - version - - install_status - - install_source - - verification_status - internal: - type: boolean - keepPoliciesUpToDate: - type: boolean - latestVersion: - type: string - license: - type: string - licensePath: - type: string - name: - type: string - notice: - type: string - owner: - additionalProperties: true - type: object - properties: - github: - type: string - type: - enum: - - elastic - - partner - - community - type: string - path: - type: string - policy_templates: - items: - additionalProperties: {} - type: object - type: array - readme: - type: string - release: - enum: - - ga - - beta - - experimental - type: string - savedObject: {} - screenshots: - items: - additionalProperties: false - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - signature_path: - type: string - source: - additionalProperties: true - type: object - properties: - license: - type: string - required: - - license - status: - type: string - title: - type: string - type: - enum: - - integration - - input - - content - type: string - vars: - items: - additionalProperties: {} - type: object - type: array - version: - type: string - required: - - savedObject - - name - - version - - title - - assets - metadata: - additionalProperties: false - type: object - properties: - has_policies: - type: boolean - required: - - has_policies - response: - additionalProperties: true - deprecated: true - type: object - properties: - agent: - additionalProperties: false - type: object - properties: - privileges: - additionalProperties: false - type: object - properties: - root: - type: boolean - asset_tags: - items: - additionalProperties: false - type: object - properties: - asset_ids: - items: - type: string - type: array - asset_types: - items: - type: string - type: array - text: - type: string - required: - - text - type: array - assets: - additionalProperties: {} - type: object - categories: - items: - type: string - type: array - conditions: - additionalProperties: true - type: object - properties: - elastic: - additionalProperties: true - type: object - properties: - capabilities: - items: - type: string - type: array - subscription: - type: string - kibana: - additionalProperties: true - type: object - properties: - version: - type: string - data_streams: - items: - additionalProperties: {} - type: object - type: array - description: - type: string - discovery: - additionalProperties: true - type: object - properties: - fields: - items: - additionalProperties: true - type: object - properties: - name: - type: string - required: - - name - type: array - download: - type: string - elasticsearch: - additionalProperties: {} - type: object - format_version: - type: string - icons: - items: - additionalProperties: true - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - installationInfo: - additionalProperties: true - type: object - properties: - additional_spaces_installed_kibana: - additionalProperties: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - type: object - created_at: - type: string - experimental_data_stream_features: - items: - additionalProperties: true - type: object - properties: - data_stream: - type: string - features: - additionalProperties: true - type: object - properties: - doc_value_only_numeric: - type: boolean - doc_value_only_other: - type: boolean - synthetic_source: - type: boolean - tsdb: - type: boolean - required: - - data_stream - - features - type: array - install_format_schema_version: - type: string - install_source: - enum: - - registry - - upload - - bundled - - custom - type: string - install_status: - enum: - - installed - - installing - - install_failed - type: string - installed_es: - items: - additionalProperties: true - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - installed_kibana: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - installed_kibana_space_id: - type: string - latest_executed_state: - additionalProperties: true - type: object - properties: - error: - type: string - name: - type: string - started_at: - type: string - required: - - name - - started_at - latest_install_failed_attempts: - items: - additionalProperties: true - type: object - properties: - created_at: - type: string - error: - additionalProperties: true - type: object - properties: - message: - type: string - name: - type: string - stack: - type: string - required: - - name - - message - target_version: - type: string - required: - - created_at - - target_version - - error - type: array - name: - type: string - namespaces: - items: - type: string - type: array - type: - type: string - updated_at: - type: string - verification_key_id: - nullable: true - type: string - verification_status: - enum: - - unverified - - verified - - unknown - type: string - version: - type: string - required: - - type - - installed_kibana - - installed_es - - name - - version - - install_status - - install_source - - verification_status - internal: - type: boolean - keepPoliciesUpToDate: - type: boolean - latestVersion: - type: string - license: - type: string - licensePath: - type: string - name: - type: string - notice: - type: string - owner: - additionalProperties: true - type: object - properties: - github: - type: string - type: - enum: - - elastic - - partner - - community - type: string - path: - type: string - policy_templates: - items: - additionalProperties: {} - type: object - type: array - readme: - type: string - release: - enum: - - ga - - beta - - experimental - type: string - savedObject: {} - screenshots: - items: - additionalProperties: false - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: + anyOf: + - additionalProperties: false + type: object + properties: + name: type: string - type: + result: + additionalProperties: false + type: object + properties: + assets: + items: + anyOf: + - additionalProperties: false + type: object + properties: + id: + type: string + originId: + type: string + type: + enum: + - dashboard + - lens + - visualization + - search + - index-pattern + - map + - ml-module + - security-rule + - csp-rule-template + - osquery-pack-asset + - osquery-saved-query + - tag + type: string + required: + - id + - type + - additionalProperties: false + type: object + properties: + deferred: + type: boolean + id: + type: string + type: + enum: + - index + - index_template + - component_template + - ingest_pipeline + - ilm_policy + - data_stream_ilm_policy + - transform + - ml_model + type: string + version: + type: string + required: + - id + - type + type: array + error: {} + installSource: + type: string + installType: + type: string + status: + enum: + - installed + - already_installed + type: string + required: + - error + - installType + version: type: string required: - - src - type: array - signature_path: - type: string - source: - additionalProperties: true - type: object - properties: - license: - type: string - required: - - license - status: - type: string - title: - type: string - type: - enum: - - integration - - input - - content - type: string - vars: - items: - additionalProperties: {} + - name + - version + - result + - additionalProperties: false type: object - type: array - version: - type: string - required: - - savedObject - - name - - version - - title - - assets + properties: + error: + anyOf: + - type: string + - {} + name: + type: string + statusCode: + type: number + required: + - name + - statusCode + - error + type: array required: - - item + - items '400': content: application/json; Elastic-Api-Version=2023-10-31: @@ -21044,9 +19221,10 @@ paths: summary: '' tags: - Elastic Package Manager (EPM) - post: - description: Install package from registry - operationId: post-fleet-epm-packages-pkgname-pkgversion + /api/fleet/epm/packages/{pkgName}/{pkgVersion}: + delete: + description: Delete package + operationId: delete-fleet-epm-packages-pkgname-pkgversion parameters: - description: The version of the API to use in: header @@ -21070,40 +19248,14 @@ paths: type: string - in: path name: pkgVersion - required: true - schema: - type: string - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: ignoreMappingUpdateErrors required: false schema: - default: false - type: boolean + type: string - in: query - name: skipDataStreamRollover + name: force required: false schema: - default: false type: boolean - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - default: false - type: boolean - ignore_constraints: - default: false - type: boolean responses: '200': content: @@ -21112,14 +19264,6 @@ paths: additionalProperties: false type: object properties: - _meta: - additionalProperties: false - type: object - properties: - install_source: - type: string - required: - - install_source items: items: anyOf: @@ -21172,62 +19316,8 @@ paths: - id - type type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array required: - items - - _meta '400': content: application/json; Elastic-Api-Version=2023-10-31: @@ -21247,9 +19337,9 @@ paths: summary: '' tags: - Elastic Package Manager (EPM) - put: - description: Update package settings - operationId: put-fleet-epm-packages-pkgname-pkgversion + get: + description: Get package + operationId: get-fleet-epm-packages-pkgname-pkgversion parameters: - description: The version of the API to use in: header @@ -21259,13 +19349,6 @@ paths: enum: - '2023-10-31' type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - in: path name: pkgName required: true @@ -21273,20 +19356,30 @@ paths: type: string - in: path name: pkgVersion - required: true + required: false + schema: + type: string + - in: query + name: ignoreUnverified + required: false + schema: + type: boolean + - in: query + name: prerelease + required: false + schema: + type: boolean + - in: query + name: full + required: false schema: - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - keepPoliciesUpToDate: - type: boolean - required: - - keepPoliciesUpToDate + type: boolean + - in: query + name: withMetadata + required: false + schema: + default: false + type: boolean responses: '200': content: @@ -21643,7 +19736,6 @@ paths: - beta - experimental type: string - savedObject: {} screenshots: items: additionalProperties: false @@ -21692,14 +19784,239 @@ paths: version: type: string required: - - savedObject - name - version - title - assets - response: + metadata: + additionalProperties: false + type: object + properties: + has_policies: + type: boolean + required: + - has_policies + required: + - item + '400': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + description: Generic Error + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + required: + - message + summary: '' + tags: + - Elastic Package Manager (EPM) + post: + description: Install package from registry + operationId: post-fleet-epm-packages-pkgname-pkgversion + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: pkgName + required: true + schema: + type: string + - in: path + name: pkgVersion + required: false + schema: + type: string + - in: query + name: prerelease + required: false + schema: + type: boolean + - in: query + name: ignoreMappingUpdateErrors + required: false + schema: + default: false + type: boolean + - in: query + name: skipDataStreamRollover + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + nullable: true + type: object + properties: + force: + default: false + type: boolean + ignore_constraints: + default: false + type: boolean + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _meta: + additionalProperties: false + type: object + properties: + install_source: + type: string + required: + - install_source + items: + items: + anyOf: + - additionalProperties: false + type: object + properties: + id: + type: string + originId: + type: string + type: + enum: + - dashboard + - lens + - visualization + - search + - index-pattern + - map + - ml-module + - security-rule + - csp-rule-template + - osquery-pack-asset + - osquery-saved-query + - tag + type: string + required: + - id + - type + - additionalProperties: false + type: object + properties: + deferred: + type: boolean + id: + type: string + type: + enum: + - index + - index_template + - component_template + - ingest_pipeline + - ilm_policy + - data_stream_ilm_policy + - transform + - ml_model + type: string + version: + type: string + required: + - id + - type + type: array + required: + - items + - _meta + '400': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + description: Generic Error + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + required: + - message + summary: '' + tags: + - Elastic Package Manager (EPM) + put: + description: Update package settings + operationId: put-fleet-epm-packages-pkgname-pkgversion + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: pkgName + required: true + schema: + type: string + - in: path + name: pkgVersion + required: false + schema: + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + keepPoliciesUpToDate: + type: boolean + required: + - keepPoliciesUpToDate + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + item: additionalProperties: true - deprecated: true type: object properties: agent: @@ -22046,7 +20363,6 @@ paths: - beta - experimental type: string - savedObject: {} screenshots: items: additionalProperties: false @@ -22095,7 +20411,6 @@ paths: version: type: string required: - - savedObject - name - version - title @@ -22500,11 +20815,6 @@ paths: items: type: string type: array - response: - deprecated: true - items: - type: string - type: array required: - items '400': diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 0dd1586ef8b37..64d227b91979d 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -21663,11 +21663,6 @@ paths: required: false schema: type: boolean - - in: query - name: experimental - required: false - schema: - type: boolean - in: query name: include_policy_templates required: false @@ -21701,27 +21696,6 @@ paths: - title - count type: array - response: - items: - additionalProperties: false - deprecated: true - type: object - properties: - count: - type: number - id: - type: string - parent_id: - type: string - parent_title: - type: string - title: - type: string - required: - - id - - title - - count - type: array required: - items '400': @@ -21864,59 +21838,6 @@ paths: - id - type type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array required: - items - _meta @@ -22045,11 +21966,6 @@ paths: required: false schema: type: boolean - - in: query - name: experimental - required: false - schema: - type: boolean - in: query name: excludeInstallStatus required: false @@ -22374,7 +22290,6 @@ paths: - beta - experimental type: string - savedObject: {} signature_path: type: string source: @@ -22403,536 +22318,135 @@ paths: version: type: string required: - - savedObject - name - version - title - id type: array - response: + required: + - items + '400': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + description: Generic Error + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + required: + - message + summary: '' + tags: + - Elastic Package Manager (EPM) + post: + description: Install package by upload + operationId: post-fleet-epm-packages + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: query + name: ignoreMappingUpdateErrors + required: false + schema: + default: false + type: boolean + - in: query + name: skipDataStreamRollover + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/gzip; application/zip; Elastic-Api-Version=2023-10-31: + schema: + format: binary + type: string + responses: + '200': + content: + application/gzip; application/zip; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _meta: + additionalProperties: false + type: object + properties: + install_source: + type: string + required: + - install_source + items: items: - additionalProperties: true - deprecated: true - type: object - properties: - categories: - items: - type: string - type: array - conditions: - additionalProperties: true + anyOf: + - additionalProperties: false type: object properties: - elastic: - additionalProperties: true - type: object - properties: - capabilities: - items: - type: string - type: array - subscription: - type: string - kibana: - additionalProperties: true - type: object - properties: - version: - type: string - data_streams: - items: - additionalProperties: {} - type: object - type: array - description: - type: string - discovery: - additionalProperties: true + id: + type: string + originId: + type: string + type: + enum: + - dashboard + - lens + - visualization + - search + - index-pattern + - map + - ml-module + - security-rule + - csp-rule-template + - osquery-pack-asset + - osquery-saved-query + - tag + type: string + required: + - id + - type + - additionalProperties: false type: object properties: - fields: - items: - additionalProperties: true - type: object - properties: - name: - type: string - required: - - name - type: array - download: - type: string - format_version: - type: string - icons: - items: - additionalProperties: true - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - id: - type: string - installationInfo: - additionalProperties: true - type: object - properties: - additional_spaces_installed_kibana: - additionalProperties: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - type: object - created_at: - type: string - experimental_data_stream_features: - items: - additionalProperties: true - type: object - properties: - data_stream: - type: string - features: - additionalProperties: true - type: object - properties: - doc_value_only_numeric: - type: boolean - doc_value_only_other: - type: boolean - synthetic_source: - type: boolean - tsdb: - type: boolean - required: - - data_stream - - features - type: array - install_format_schema_version: - type: string - install_source: - enum: - - registry - - upload - - bundled - - custom - type: string - install_status: - enum: - - installed - - installing - - install_failed - type: string - installed_es: - items: - additionalProperties: true - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - installed_kibana: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - installed_kibana_space_id: - type: string - latest_executed_state: - additionalProperties: true - type: object - properties: - error: - type: string - name: - type: string - started_at: - type: string - required: - - name - - started_at - latest_install_failed_attempts: - items: - additionalProperties: true - type: object - properties: - created_at: - type: string - error: - additionalProperties: true - type: object - properties: - message: - type: string - name: - type: string - stack: - type: string - required: - - name - - message - target_version: - type: string - required: - - created_at - - target_version - - error - type: array - name: + deferred: + type: boolean + id: type: string - namespaces: - items: - type: string - type: array type: - type: string - updated_at: - type: string - verification_key_id: - nullable: true - type: string - verification_status: enum: - - unverified - - verified - - unknown + - index + - index_template + - component_template + - ingest_pipeline + - ilm_policy + - data_stream_ilm_policy + - transform + - ml_model type: string version: type: string required: - - type - - installed_kibana - - installed_es - - name - - version - - install_status - - install_source - - verification_status - integration: - type: string - internal: - type: boolean - latestVersion: - type: string - name: - type: string - owner: - additionalProperties: true - type: object - properties: - github: - type: string - type: - enum: - - elastic - - partner - - community - type: string - path: - type: string - policy_templates: - items: - additionalProperties: {} - type: object - type: array - readme: - type: string - release: - enum: - - ga - - beta - - experimental - type: string - savedObject: {} - signature_path: - type: string - source: - additionalProperties: true - type: object - properties: - license: - type: string - required: - - license - status: - type: string - title: - type: string - type: - enum: - - integration - - input - - content - type: string - vars: - items: - additionalProperties: {} - type: object - type: array - version: - type: string - required: - - savedObject - - name - - version - - title - - id - type: array - required: - - items - '400': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - required: - - message - summary: '' - tags: - - Elastic Package Manager (EPM) - post: - description: Install package by upload - operationId: post-fleet-epm-packages - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: query - name: ignoreMappingUpdateErrors - required: false - schema: - default: false - type: boolean - - in: query - name: skipDataStreamRollover - required: false - schema: - default: false - type: boolean - requestBody: - content: - application/gzip; application/zip; Elastic-Api-Version=2023-10-31: - schema: - format: binary - type: string - responses: - '200': - content: - application/gzip; application/zip; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - _meta: - additionalProperties: false - type: object - properties: - install_source: - type: string - required: - - install_source - items: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id + - id - type type: array required: @@ -23022,1442 +22536,105 @@ paths: properties: items: items: - anyOf: - - additionalProperties: false - type: object - properties: - name: - type: string - result: - additionalProperties: false - type: object - properties: - assets: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - error: {} - installSource: - type: string - installType: - type: string - status: - enum: - - installed - - already_installed - type: string - required: - - error - - installType - version: - type: string - required: - - name - - version - - result - - additionalProperties: false - type: object - properties: - error: - anyOf: - - type: string - - {} - name: - type: string - statusCode: - type: number - required: - - name - - statusCode - - error - type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - name: - type: string - result: - additionalProperties: false - type: object - properties: - assets: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - error: {} - installSource: - type: string - installType: - type: string - status: - enum: - - installed - - already_installed - type: string - required: - - error - - installType - version: - type: string - required: - - name - - version - - result - - additionalProperties: false - type: object - properties: - error: - anyOf: - - type: string - - {} - name: - type: string - statusCode: - type: number - required: - - name - - statusCode - - error - type: array - required: - - items - '400': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - required: - - message - summary: '' - tags: - - Elastic Package Manager (EPM) - /api/fleet/epm/packages/{pkgkey}: - delete: - operationId: delete-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - type: boolean - required: - - force - responses: {} - summary: '' - tags: [] - get: - operationId: get-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - - in: query - name: ignoreUnverified - required: false - schema: - type: boolean - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: full - required: false - schema: - type: boolean - - in: query - name: withMetadata - required: false - schema: - default: false - type: boolean - responses: {} - summary: '' - tags: [] - post: - operationId: post-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: ignoreMappingUpdateErrors - required: false - schema: - default: false - type: boolean - - in: query - name: skipDataStreamRollover - required: false - schema: - default: false - type: boolean - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - type: boolean - required: - - force - responses: {} - summary: '' - tags: [] - put: - operationId: put-fleet-epm-packages-pkgkey - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgkey - required: true - schema: - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - keepPoliciesUpToDate: - type: boolean - required: - - keepPoliciesUpToDate - responses: {} - summary: '' - tags: [] - /api/fleet/epm/packages/{pkgName}/{pkgVersion}: - delete: - description: Delete package - operationId: delete-fleet-epm-packages-pkgname-pkgversion - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - - in: path - name: pkgName - required: true - schema: - type: string - - in: path - name: pkgVersion - required: true - schema: - type: string - - in: query - name: force - required: false - schema: - type: boolean - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - type: boolean - required: - - force - responses: - '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - items: - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - required: - - items - '400': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - error: - type: string - message: - type: string - statusCode: - type: number - required: - - message - summary: '' - tags: - - Elastic Package Manager (EPM) - get: - description: Get package - operationId: get-fleet-epm-packages-pkgname-pkgversion - parameters: - - description: The version of the API to use - in: header - name: elastic-api-version - schema: - default: '2023-10-31' - enum: - - '2023-10-31' - type: string - - in: path - name: pkgName - required: true - schema: - type: string - - in: path - name: pkgVersion - required: true - schema: - type: string - - in: query - name: ignoreUnverified - required: false - schema: - type: boolean - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: full - required: false - schema: - type: boolean - - in: query - name: withMetadata - required: false - schema: - default: false - type: boolean - responses: - '200': - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - item: - additionalProperties: true - type: object - properties: - agent: - additionalProperties: false - type: object - properties: - privileges: - additionalProperties: false - type: object - properties: - root: - type: boolean - asset_tags: - items: - additionalProperties: false - type: object - properties: - asset_ids: - items: - type: string - type: array - asset_types: - items: - type: string - type: array - text: - type: string - required: - - text - type: array - assets: - additionalProperties: {} - type: object - categories: - items: - type: string - type: array - conditions: - additionalProperties: true - type: object - properties: - elastic: - additionalProperties: true - type: object - properties: - capabilities: - items: - type: string - type: array - subscription: - type: string - kibana: - additionalProperties: true - type: object - properties: - version: - type: string - data_streams: - items: - additionalProperties: {} - type: object - type: array - description: - type: string - discovery: - additionalProperties: true - type: object - properties: - fields: - items: - additionalProperties: true - type: object - properties: - name: - type: string - required: - - name - type: array - download: - type: string - elasticsearch: - additionalProperties: {} - type: object - format_version: - type: string - icons: - items: - additionalProperties: true - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - installationInfo: - additionalProperties: true - type: object - properties: - additional_spaces_installed_kibana: - additionalProperties: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - type: object - created_at: - type: string - experimental_data_stream_features: - items: - additionalProperties: true - type: object - properties: - data_stream: - type: string - features: - additionalProperties: true - type: object - properties: - doc_value_only_numeric: - type: boolean - doc_value_only_other: - type: boolean - synthetic_source: - type: boolean - tsdb: - type: boolean - required: - - data_stream - - features - type: array - install_format_schema_version: - type: string - install_source: - enum: - - registry - - upload - - bundled - - custom - type: string - install_status: - enum: - - installed - - installing - - install_failed - type: string - installed_es: - items: - additionalProperties: true - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - installed_kibana: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - installed_kibana_space_id: - type: string - latest_executed_state: - additionalProperties: true - type: object - properties: - error: - type: string - name: - type: string - started_at: - type: string - required: - - name - - started_at - latest_install_failed_attempts: - items: - additionalProperties: true - type: object - properties: - created_at: - type: string - error: - additionalProperties: true - type: object - properties: - message: - type: string - name: - type: string - stack: - type: string - required: - - name - - message - target_version: - type: string - required: - - created_at - - target_version - - error - type: array - name: - type: string - namespaces: - items: - type: string - type: array - type: - type: string - updated_at: - type: string - verification_key_id: - nullable: true - type: string - verification_status: - enum: - - unverified - - verified - - unknown - type: string - version: - type: string - required: - - type - - installed_kibana - - installed_es - - name - - version - - install_status - - install_source - - verification_status - internal: - type: boolean - keepPoliciesUpToDate: - type: boolean - latestVersion: - type: string - license: - type: string - licensePath: - type: string - name: - type: string - notice: - type: string - owner: - additionalProperties: true - type: object - properties: - github: - type: string - type: - enum: - - elastic - - partner - - community - type: string - path: - type: string - policy_templates: - items: - additionalProperties: {} - type: object - type: array - readme: - type: string - release: - enum: - - ga - - beta - - experimental - type: string - savedObject: {} - screenshots: - items: - additionalProperties: false - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - signature_path: - type: string - source: - additionalProperties: true - type: object - properties: - license: - type: string - required: - - license - status: - type: string - title: - type: string - type: - enum: - - integration - - input - - content - type: string - vars: - items: - additionalProperties: {} - type: object - type: array - version: - type: string - required: - - savedObject - - name - - version - - title - - assets - metadata: - additionalProperties: false - type: object - properties: - has_policies: - type: boolean - required: - - has_policies - response: - additionalProperties: true - deprecated: true - type: object - properties: - agent: - additionalProperties: false - type: object - properties: - privileges: - additionalProperties: false - type: object - properties: - root: - type: boolean - asset_tags: - items: - additionalProperties: false - type: object - properties: - asset_ids: - items: - type: string - type: array - asset_types: - items: - type: string - type: array - text: - type: string - required: - - text - type: array - assets: - additionalProperties: {} - type: object - categories: - items: - type: string - type: array - conditions: - additionalProperties: true - type: object - properties: - elastic: - additionalProperties: true - type: object - properties: - capabilities: - items: - type: string - type: array - subscription: - type: string - kibana: - additionalProperties: true - type: object - properties: - version: - type: string - data_streams: - items: - additionalProperties: {} - type: object - type: array - description: - type: string - discovery: - additionalProperties: true - type: object - properties: - fields: - items: - additionalProperties: true - type: object - properties: - name: - type: string - required: - - name - type: array - download: - type: string - elasticsearch: - additionalProperties: {} - type: object - format_version: - type: string - icons: - items: - additionalProperties: true - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: - type: string - type: - type: string - required: - - src - type: array - installationInfo: - additionalProperties: true - type: object - properties: - additional_spaces_installed_kibana: - additionalProperties: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - type: object - created_at: - type: string - experimental_data_stream_features: - items: - additionalProperties: true - type: object - properties: - data_stream: - type: string - features: - additionalProperties: true - type: object - properties: - doc_value_only_numeric: - type: boolean - doc_value_only_other: - type: boolean - synthetic_source: - type: boolean - tsdb: - type: boolean - required: - - data_stream - - features - type: array - install_format_schema_version: - type: string - install_source: - enum: - - registry - - upload - - bundled - - custom - type: string - install_status: - enum: - - installed - - installing - - install_failed - type: string - installed_es: - items: - additionalProperties: true - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array - installed_kibana: - items: - additionalProperties: true - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - type: array - installed_kibana_space_id: - type: string - latest_executed_state: - additionalProperties: true - type: object - properties: - error: - type: string - name: - type: string - started_at: - type: string - required: - - name - - started_at - latest_install_failed_attempts: - items: - additionalProperties: true - type: object - properties: - created_at: - type: string - error: - additionalProperties: true - type: object - properties: - message: - type: string - name: - type: string - stack: - type: string - required: - - name - - message - target_version: - type: string - required: - - created_at - - target_version - - error - type: array - name: - type: string - namespaces: - items: - type: string - type: array - type: - type: string - updated_at: - type: string - verification_key_id: - nullable: true - type: string - verification_status: - enum: - - unverified - - verified - - unknown - type: string - version: - type: string - required: - - type - - installed_kibana - - installed_es - - name - - version - - install_status - - install_source - - verification_status - internal: - type: boolean - keepPoliciesUpToDate: - type: boolean - latestVersion: - type: string - license: - type: string - licensePath: - type: string - name: - type: string - notice: - type: string - owner: - additionalProperties: true - type: object - properties: - github: - type: string - type: - enum: - - elastic - - partner - - community - type: string - path: - type: string - policy_templates: - items: - additionalProperties: {} - type: object - type: array - readme: - type: string - release: - enum: - - ga - - beta - - experimental - type: string - savedObject: {} - screenshots: - items: - additionalProperties: false - type: object - properties: - dark_mode: - type: boolean - path: - type: string - size: - type: string - src: - type: string - title: + anyOf: + - additionalProperties: false + type: object + properties: + name: type: string - type: + result: + additionalProperties: false + type: object + properties: + assets: + items: + anyOf: + - additionalProperties: false + type: object + properties: + id: + type: string + originId: + type: string + type: + enum: + - dashboard + - lens + - visualization + - search + - index-pattern + - map + - ml-module + - security-rule + - csp-rule-template + - osquery-pack-asset + - osquery-saved-query + - tag + type: string + required: + - id + - type + - additionalProperties: false + type: object + properties: + deferred: + type: boolean + id: + type: string + type: + enum: + - index + - index_template + - component_template + - ingest_pipeline + - ilm_policy + - data_stream_ilm_policy + - transform + - ml_model + type: string + version: + type: string + required: + - id + - type + type: array + error: {} + installSource: + type: string + installType: + type: string + status: + enum: + - installed + - already_installed + type: string + required: + - error + - installType + version: type: string required: - - src - type: array - signature_path: - type: string - source: - additionalProperties: true - type: object - properties: - license: - type: string - required: - - license - status: - type: string - title: - type: string - type: - enum: - - integration - - input - - content - type: string - vars: - items: - additionalProperties: {} + - name + - version + - result + - additionalProperties: false type: object - type: array - version: - type: string - required: - - savedObject - - name - - version - - title - - assets + properties: + error: + anyOf: + - type: string + - {} + name: + type: string + statusCode: + type: number + required: + - name + - statusCode + - error + type: array required: - - item + - items '400': content: application/json; Elastic-Api-Version=2023-10-31: @@ -24477,9 +22654,10 @@ paths: summary: '' tags: - Elastic Package Manager (EPM) - post: - description: Install package from registry - operationId: post-fleet-epm-packages-pkgname-pkgversion + /api/fleet/epm/packages/{pkgName}/{pkgVersion}: + delete: + description: Delete package + operationId: delete-fleet-epm-packages-pkgname-pkgversion parameters: - description: The version of the API to use in: header @@ -24503,40 +22681,14 @@ paths: type: string - in: path name: pkgVersion - required: true - schema: - type: string - - in: query - name: prerelease - required: false - schema: - type: boolean - - in: query - name: ignoreMappingUpdateErrors required: false schema: - default: false - type: boolean + type: string - in: query - name: skipDataStreamRollover + name: force required: false schema: - default: false type: boolean - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - nullable: true - type: object - properties: - force: - default: false - type: boolean - ignore_constraints: - default: false - type: boolean responses: '200': content: @@ -24545,14 +22697,6 @@ paths: additionalProperties: false type: object properties: - _meta: - additionalProperties: false - type: object - properties: - install_source: - type: string - required: - - install_source items: items: anyOf: @@ -24605,62 +22749,8 @@ paths: - id - type type: array - response: - deprecated: true - items: - anyOf: - - additionalProperties: false - type: object - properties: - id: - type: string - originId: - type: string - type: - enum: - - dashboard - - lens - - visualization - - search - - index-pattern - - map - - ml-module - - security-rule - - csp-rule-template - - osquery-pack-asset - - osquery-saved-query - - tag - type: string - required: - - id - - type - - additionalProperties: false - type: object - properties: - deferred: - type: boolean - id: - type: string - type: - enum: - - index - - index_template - - component_template - - ingest_pipeline - - ilm_policy - - data_stream_ilm_policy - - transform - - ml_model - type: string - version: - type: string - required: - - id - - type - type: array required: - items - - _meta '400': content: application/json; Elastic-Api-Version=2023-10-31: @@ -24680,9 +22770,9 @@ paths: summary: '' tags: - Elastic Package Manager (EPM) - put: - description: Update package settings - operationId: put-fleet-epm-packages-pkgname-pkgversion + get: + description: Get package + operationId: get-fleet-epm-packages-pkgname-pkgversion parameters: - description: The version of the API to use in: header @@ -24692,13 +22782,6 @@ paths: enum: - '2023-10-31' type: string - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - in: path name: pkgName required: true @@ -24706,20 +22789,30 @@ paths: type: string - in: path name: pkgVersion - required: true + required: false + schema: + type: string + - in: query + name: ignoreUnverified + required: false + schema: + type: boolean + - in: query + name: prerelease + required: false + schema: + type: boolean + - in: query + name: full + required: false schema: - type: string - requestBody: - content: - application/json; Elastic-Api-Version=2023-10-31: - schema: - additionalProperties: false - type: object - properties: - keepPoliciesUpToDate: - type: boolean - required: - - keepPoliciesUpToDate + type: boolean + - in: query + name: withMetadata + required: false + schema: + default: false + type: boolean responses: '200': content: @@ -25076,7 +23169,6 @@ paths: - beta - experimental type: string - savedObject: {} screenshots: items: additionalProperties: false @@ -25125,14 +23217,239 @@ paths: version: type: string required: - - savedObject - name - version - title - assets - response: + metadata: + additionalProperties: false + type: object + properties: + has_policies: + type: boolean + required: + - has_policies + required: + - item + '400': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + description: Generic Error + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + required: + - message + summary: '' + tags: + - Elastic Package Manager (EPM) + post: + description: Install package from registry + operationId: post-fleet-epm-packages-pkgname-pkgversion + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: pkgName + required: true + schema: + type: string + - in: path + name: pkgVersion + required: false + schema: + type: string + - in: query + name: prerelease + required: false + schema: + type: boolean + - in: query + name: ignoreMappingUpdateErrors + required: false + schema: + default: false + type: boolean + - in: query + name: skipDataStreamRollover + required: false + schema: + default: false + type: boolean + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + nullable: true + type: object + properties: + force: + default: false + type: boolean + ignore_constraints: + default: false + type: boolean + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + _meta: + additionalProperties: false + type: object + properties: + install_source: + type: string + required: + - install_source + items: + items: + anyOf: + - additionalProperties: false + type: object + properties: + id: + type: string + originId: + type: string + type: + enum: + - dashboard + - lens + - visualization + - search + - index-pattern + - map + - ml-module + - security-rule + - csp-rule-template + - osquery-pack-asset + - osquery-saved-query + - tag + type: string + required: + - id + - type + - additionalProperties: false + type: object + properties: + deferred: + type: boolean + id: + type: string + type: + enum: + - index + - index_template + - component_template + - ingest_pipeline + - ilm_policy + - data_stream_ilm_policy + - transform + - ml_model + type: string + version: + type: string + required: + - id + - type + type: array + required: + - items + - _meta + '400': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + description: Generic Error + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: number + required: + - message + summary: '' + tags: + - Elastic Package Manager (EPM) + put: + description: Update package settings + operationId: put-fleet-epm-packages-pkgname-pkgversion + parameters: + - description: The version of the API to use + in: header + name: elastic-api-version + schema: + default: '2023-10-31' + enum: + - '2023-10-31' + type: string + - description: A required header to protect against CSRF attacks + in: header + name: kbn-xsrf + required: true + schema: + example: 'true' + type: string + - in: path + name: pkgName + required: true + schema: + type: string + - in: path + name: pkgVersion + required: false + schema: + type: string + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + keepPoliciesUpToDate: + type: boolean + required: + - keepPoliciesUpToDate + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + additionalProperties: false + type: object + properties: + item: additionalProperties: true - deprecated: true type: object properties: agent: @@ -25479,7 +23796,6 @@ paths: - beta - experimental type: string - savedObject: {} screenshots: items: additionalProperties: false @@ -25528,7 +23844,6 @@ paths: version: type: string required: - - savedObject - name - version - title @@ -25933,11 +24248,6 @@ paths: items: type: string type: array - response: - deprecated: true - items: - type: string - type: array required: - items '400': diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 4b40b4fb51092..436d476db16d2 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -23,19 +23,21 @@ export const LIMITED_CONCURRENCY_ROUTE_TAG = 'ingest:limited-concurrency'; const EPM_PACKAGES_MANY = `${EPM_API_ROOT}/packages`; const EPM_PACKAGES_INSTALLED = `${EPM_API_ROOT}/packages/installed`; const EPM_PACKAGES_BULK = `${EPM_PACKAGES_MANY}/_bulk`; -const EPM_PACKAGES_ONE_DEPRECATED = `${EPM_PACKAGES_MANY}/{pkgkey}`; +const EPM_PACKAGES_ONE_WITHOUT_VERSION = `${EPM_PACKAGES_MANY}/{pkgName}`; const EPM_PACKAGES_ONE = `${EPM_PACKAGES_MANY}/{pkgName}/{pkgVersion}`; +const EPM_PACKAGES_ONE_WITH_OPTIONAL_VERSION = `${EPM_PACKAGES_MANY}/{pkgName}/{pkgVersion?}`; export const EPM_API_ROUTES = { BULK_INSTALL_PATTERN: EPM_PACKAGES_BULK, LIST_PATTERN: EPM_PACKAGES_MANY, INSTALLED_LIST_PATTERN: EPM_PACKAGES_INSTALLED, LIMITED_LIST_PATTERN: `${EPM_PACKAGES_MANY}/limited`, - INFO_PATTERN: EPM_PACKAGES_ONE, + INFO_WITHOUT_VERSION_PATTERN: EPM_PACKAGES_ONE_WITHOUT_VERSION, + INFO_PATTERN: EPM_PACKAGES_ONE_WITH_OPTIONAL_VERSION, DATA_STREAMS_PATTERN: `${EPM_API_ROOT}/data_streams`, - INSTALL_FROM_REGISTRY_PATTERN: EPM_PACKAGES_ONE, + INSTALL_FROM_REGISTRY_PATTERN: EPM_PACKAGES_ONE_WITH_OPTIONAL_VERSION, INSTALL_BY_UPLOAD_PATTERN: EPM_PACKAGES_MANY, CUSTOM_INTEGRATIONS_PATTERN: `${EPM_API_ROOT}/custom_integrations`, - DELETE_PATTERN: EPM_PACKAGES_ONE, + DELETE_PATTERN: EPM_PACKAGES_ONE_WITH_OPTIONAL_VERSION, INSTALL_KIBANA_ASSETS_PATTERN: `${EPM_PACKAGES_ONE}/kibana_assets`, DELETE_KIBANA_ASSETS_PATTERN: `${EPM_PACKAGES_ONE}/kibana_assets`, FILEPATH_PATTERN: `${EPM_PACKAGES_ONE}/{filePath*}`, @@ -45,10 +47,6 @@ export const EPM_API_ROUTES = { BULK_ASSETS_PATTERN: `${EPM_API_ROOT}/bulk_assets`, INPUTS_PATTERN: `${EPM_API_ROOT}/templates/{pkgName}/{pkgVersion}/inputs`, - INFO_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, - INSTALL_FROM_REGISTRY_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, - DELETE_PATTERN_DEPRECATED: EPM_PACKAGES_ONE_DEPRECATED, - REAUTHORIZE_TRANSFORMS: `${EPM_PACKAGES_ONE}/transforms/authorize`, }; diff --git a/x-pack/plugins/fleet/common/services/route.test.ts b/x-pack/plugins/fleet/common/services/route.test.ts new file mode 100644 index 0000000000000..1075f4f2605ea --- /dev/null +++ b/x-pack/plugins/fleet/common/services/route.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { epmRouteService } from './routes'; + +describe('Route services', () => { + describe('epmRouteService', () => { + describe('getInfoPath', () => { + it('should generate path with pkgVersion', () => { + expect(epmRouteService.getInfoPath('test', '1.0.0')).toBe( + '/api/fleet/epm/packages/test/1.0.0' + ); + }); + it('should generate path without pkgVersion', () => { + expect(epmRouteService.getInfoPath('test')).toBe('/api/fleet/epm/packages/test'); + }); + }); + describe('getInstallPath', () => { + it('should generate path with pkgVersion', () => { + expect(epmRouteService.getInstallPath('test', '1.0.0')).toBe( + '/api/fleet/epm/packages/test/1.0.0' + ); + }); + it('should generate path without pkgVersion', () => { + expect(epmRouteService.getInstallPath('test')).toBe('/api/fleet/epm/packages/test'); + }); + }); + describe('getRemovePath', () => { + it('should generate path with pkgVersion', () => { + expect(epmRouteService.getRemovePath('test', '1.0.0')).toBe( + '/api/fleet/epm/packages/test/1.0.0' + ); + }); + it('should generate path without pkgVersion', () => { + expect(epmRouteService.getRemovePath('test')).toBe('/api/fleet/epm/packages/test'); + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/common/services/routes.ts b/x-pack/plugins/fleet/common/services/routes.ts index 520a71e1bdc0a..56f7096cf9766 100644 --- a/x-pack/plugins/fleet/common/services/routes.ts +++ b/x-pack/plugins/fleet/common/services/routes.ts @@ -47,11 +47,14 @@ export const epmRouteService = { getInfoPath: (pkgName: string, pkgVersion?: string) => { if (pkgVersion) { return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgName}', pkgName).replace( - '{pkgVersion}', + '{pkgVersion?}', pkgVersion ); } else { - return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgName}', pkgName).replace('/{pkgVersion}', ''); + return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgName}', pkgName).replace( + '/{pkgVersion?}', + '' + ); } }, @@ -63,20 +66,32 @@ export const epmRouteService = { return `${EPM_API_ROOT}${filePath.replace('/package', '/packages')}`; }, - getInstallPath: (pkgName: string, pkgVersion: string) => { - return EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN.replace('{pkgName}', pkgName) - .replace('{pkgVersion}', pkgVersion) - .replace(/\/$/, ''); // trim trailing slash + getInstallPath: (pkgName: string, pkgVersion?: string) => { + if (pkgVersion) { + return EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN.replace('{pkgName}', pkgName) + .replace('{pkgVersion?}', pkgVersion) + .replace(/\/$/, ''); // trim trailing slash + } else { + return EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN.replace('{pkgName}', pkgName) + .replace('/{pkgVersion?}', '') + .replace(/\/$/, ''); // trim trailing slash + } }, getBulkInstallPath: () => { return EPM_API_ROUTES.BULK_INSTALL_PATTERN; }, - getRemovePath: (pkgName: string, pkgVersion: string) => { - return EPM_API_ROUTES.DELETE_PATTERN.replace('{pkgName}', pkgName) - .replace('{pkgVersion}', pkgVersion) - .replace(/\/$/, ''); // trim trailing slash + getRemovePath: (pkgName: string, pkgVersion?: string) => { + if (pkgVersion) { + return EPM_API_ROUTES.DELETE_PATTERN.replace('{pkgName}', pkgName) + .replace('{pkgVersion?}', pkgVersion) + .replace(/\/$/, ''); // trim trailing slash + } else { + return EPM_API_ROUTES.DELETE_PATTERN.replace('{pkgName}', pkgName) + .replace('/{pkgVersion?}', '') + .replace(/\/$/, ''); // trim trailing slash + } }, getInstallKibanaAssetsPath: (pkgName: string, pkgVersion: string) => { diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 827130d802f22..f1cd9e5ee4a7f 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -546,8 +546,8 @@ export type PackageList = PackageListItem[]; export type PackageListItem = Installable & { id: string; integration?: string; - installationInfo?: InstallationInfo; savedObject?: InstallableSavedObject; + installationInfo?: InstallationInfo; }; export type PackagesGroupedByStatus = Record, PackageList>; export type PackageInfo = diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index e8dee14e40b30..68c09eeb5e9dc 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -25,8 +25,6 @@ import type { export interface GetCategoriesRequest { query: { - // deprecated in 8.6 - experimental?: boolean; prerelease?: boolean; include_policy_templates?: boolean; }; @@ -34,15 +32,11 @@ export interface GetCategoriesRequest { export interface GetCategoriesResponse { items: CategorySummaryList; - // deprecated in 8.0 - response?: CategorySummaryList; } export interface GetPackagesRequest { query: { category?: string; - // deprecated in 8.6 - experimental?: boolean; prerelease?: boolean; excludeInstallStatus?: boolean; }; @@ -50,8 +44,6 @@ export interface GetPackagesRequest { export interface GetPackagesResponse { items: PackageList; - // deprecated in 8.0 - response?: PackageList; } export interface InstalledPackage { @@ -79,8 +71,6 @@ export interface GetEpmDataStreamsResponse { } export interface GetLimitedPackagesResponse { items: string[]; - // deprecated in 8.0 - response?: string[]; } export interface GetFileRequest { @@ -93,8 +83,6 @@ export interface GetFileRequest { export interface GetInfoRequest { params: { - // deprecated in 8.0 - pkgkey?: string; pkgName: string; pkgVersion: string; }; @@ -103,14 +91,10 @@ export interface GetInfoRequest { export interface GetInfoResponse { item: PackageInfo; metadata?: PackageMetadata; - // deprecated in 8.0 - response?: PackageInfo; } export interface UpdatePackageRequest { params: { - // deprecated in 8.0 - pkgkey?: string; pkgName: string; pkgVersion: string; }; @@ -121,8 +105,6 @@ export interface UpdatePackageRequest { export interface UpdatePackageResponse { item: PackageInfo; - // deprecated in 8.0 - response?: PackageInfo; } export interface GetStatsRequest { @@ -137,8 +119,6 @@ export interface GetStatsResponse { export interface InstallPackageRequest { params: { - // deprecated in 8.0 - pkgkey?: string; pkgName: string; pkgVersion: string; }; @@ -149,8 +129,6 @@ export interface InstallPackageResponse { _meta: { install_source: InstallSource; }; - // deprecated in 8.0 - response?: AssetReference[]; } export interface IBulkInstallPackageHTTPError { @@ -175,8 +153,6 @@ export interface BulkInstallPackageInfo { export interface BulkInstallPackagesResponse { items: Array; - // deprecated in 8.0 - response?: Array; } export interface BulkInstallPackagesRequest { @@ -191,8 +167,6 @@ export interface MessageResponse { export interface DeletePackageRequest { params: { - // deprecated in 8.0 - pkgkey?: string; pkgName: string; pkgVersion: string; }; @@ -202,8 +176,6 @@ export interface DeletePackageRequest { } export interface DeletePackageResponse { - // deprecated in 8.0 - response?: AssetReference[]; items: AssetReference[]; } export interface GetVerificationKeyIdResponse { diff --git a/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx b/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx index 905f2c82e85c7..f3b7b0cb77f84 100644 --- a/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx +++ b/x-pack/plugins/fleet/public/components/home_integration/tutorial_module_notice.tsx @@ -23,8 +23,8 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName } const pkgInfo = !isLoading && - packagesData?.response && - packagesData.response.find((pkg) => pkg.name === moduleName && pkg.name !== FLEET_APM_PACKAGE); // APM needs special handling + packagesData?.items && + packagesData.items.find((pkg) => pkg.name === moduleName && pkg.name !== FLEET_APM_PACKAGE); // APM needs special handling if (hasIntegrationsPermissions && pkgInfo) { return ( diff --git a/x-pack/plugins/fleet/public/search_provider.test.ts b/x-pack/plugins/fleet/public/search_provider.test.ts index 5f95eef60546c..68ba3042a8e76 100644 --- a/x-pack/plugins/fleet/public/search_provider.test.ts +++ b/x-pack/plugins/fleet/public/search_provider.test.ts @@ -133,7 +133,7 @@ describe('Package search provider', () => { test('returns formatted results', () => { getTestScheduler().run(({ expectObservable, hot }) => { mockSendGetPackages.mockReturnValue( - hot('--(a|)', { a: { data: { response: testResponse } } }) + hot('--(a|)', { a: { data: { items: testResponse } } }) ); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any @@ -217,7 +217,7 @@ describe('Package search provider', () => { test('calls EPR once only', () => { getTestScheduler().run(({ hot }) => { - mockSendGetPackages.mockReturnValue(hot('--(a|)', { a: { data: { response: [] } } })); + mockSendGetPackages.mockReturnValue(hot('--(a|)', { a: { data: { items: [] } } })); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any ); @@ -237,7 +237,7 @@ describe('Package search provider', () => { test('completes without returning results if aborted', () => { getTestScheduler().run(({ expectObservable, hot }) => { - mockSendGetPackages.mockReturnValue(hot('--(a|)', { a: { data: { response: [] } } })); + mockSendGetPackages.mockReturnValue(hot('--(a|)', { a: { data: { items: [] } } })); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any ); @@ -258,7 +258,7 @@ describe('Package search provider', () => { test('respect maximum results', () => { getTestScheduler().run(({ hot, expectObservable }) => { mockSendGetPackages.mockReturnValue( - hot('--(a|)', { a: { data: { response: testResponse } } }) + hot('--(a|)', { a: { data: { items: testResponse } } }) ); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any @@ -292,7 +292,7 @@ describe('Package search provider', () => { test('without packages tag, without search term', () => { getTestScheduler().run(({ hot, expectObservable }) => { mockSendGetPackages.mockReturnValue( - hot('--(a|)', { a: { data: { response: testResponse } } }) + hot('--(a|)', { a: { data: { items: testResponse } } }) ); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any @@ -314,7 +314,7 @@ describe('Package search provider', () => { test('with integration tag, with no search term', () => { getTestScheduler().run(({ hot, expectObservable }) => { mockSendGetPackages.mockReturnValue( - hot('--(a|)', { a: { data: { response: testResponse } } }) + hot('--(a|)', { a: { data: { items: testResponse } } }) ); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any @@ -397,7 +397,7 @@ describe('Package search provider', () => { test('with integration tag, with search term', () => { getTestScheduler().run(({ hot, expectObservable }) => { mockSendGetPackages.mockReturnValue( - hot('--(a|)', { a: { data: { response: testResponse } } }) + hot('--(a|)', { a: { data: { items: testResponse } } }) ); setupMock.getStartServices.mockReturnValue( hot('--(a|)', { a: [coreMock.createStart()] }) as any diff --git a/x-pack/plugins/fleet/public/search_provider.ts b/x-pack/plugins/fleet/public/search_provider.ts index ce1ddd10ac5a2..a6810633c428e 100644 --- a/x-pack/plugins/fleet/public/search_provider.ts +++ b/x-pack/plugins/fleet/public/search_provider.ts @@ -30,7 +30,7 @@ const createPackages$ = () => if (error) { throw error; } - return data?.response ?? []; + return data?.items ?? []; }), shareReplay(1) ); @@ -86,7 +86,7 @@ export const createPackageSearchProvider = (core: CoreSetup): GlobalSearchResult shareReplay(1) ); - let packages$: undefined | Observable; + let packages$: undefined | Observable; const getPackages$ = () => { if (!packages$) { diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 7d7fea6d693a7..1fcd0e26a6ef0 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -9,7 +9,7 @@ import type { TypeOf } from '@kbn/config-schema'; import semverValid from 'semver/functions/valid'; import type { HttpResponseOptions } from '@kbn/core/server'; -import { pick } from 'lodash'; +import { omit, pick } from 'lodash'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../common'; @@ -98,12 +98,11 @@ export const getCategoriesHandler: FleetRequestHandler< TypeOf > = async (context, request, response) => { try { - const res = await getCategories({ + const items = await getCategories({ ...request.query, }); const body: GetCategoriesResponse = { - items: res, - response: res, + items, }; return response.ok({ body, headers: { ...CACHE_CONTROL_10_MINUTES_HEADER } }); } catch (error) { @@ -124,7 +123,6 @@ export const getListHandler: FleetRequestHandler< const flattenedRes = res.map((pkg) => soToInstallationInfo(pkg)) as PackageList; const body: GetPackagesResponse = { items: flattenedRes, - response: res, }; return response.ok({ body, @@ -199,7 +197,6 @@ export const getLimitedListHandler: FleetRequestHandler< }); const body: GetLimitedPackagesResponse = { items: res, - response: res, }; return response.ok({ body, @@ -460,7 +457,6 @@ export const bulkInstallPackagesFromRegistryHandler: FleetRequestHandler< const payload = bulkInstalledResponses.map(bulkInstallServiceResponseToHttpEntry); const body: BulkInstallPackagesResponse = { items: payload, - response: payload, }; return response.ok({ body }); }; @@ -494,7 +490,6 @@ export const installPackageByUploadHandler: FleetRequestHandler< if (!res.error) { const body: InstallPackageResponse = { items: res.assets || [], - response: res.assets || [], _meta: { install_source: res.installSource ?? installSource, }, @@ -519,8 +514,7 @@ export const installPackageByUploadHandler: FleetRequestHandler< export const deletePackageHandler: FleetRequestHandler< TypeOf, - TypeOf, - TypeOf + TypeOf > = async (context, request, response) => { try { const { pkgName, pkgVersion } = request.params; @@ -676,8 +670,7 @@ const soToInstallationInfo = (pkg: PackageListItem | PackageInfo) => { }; return { - // When savedObject gets removed, replace `pkg` with `...omit(pkg, 'savedObject')` - ...pkg, + ...omit(pkg, 'savedObject'), installationInfo, }; } diff --git a/x-pack/plugins/fleet/server/routes/epm/index.test.ts b/x-pack/plugins/fleet/server/routes/epm/index.test.ts index a054b02aae1d1..d736cb7c318ba 100644 --- a/x-pack/plugins/fleet/server/routes/epm/index.test.ts +++ b/x-pack/plugins/fleet/server/routes/epm/index.test.ts @@ -323,7 +323,6 @@ describe('schema validation', () => { }; const expectedResponse: GetCategoriesResponse = { items: [category], - response: [category], }; (getCategoriesHandler as jest.Mock).mockImplementation((ctx, request, res) => { return res.ok({ body: expectedResponse }); @@ -386,7 +385,6 @@ describe('schema validation', () => { }; const expectedResponse: GetPackagesResponse = { items: [packageItem], - response: [packageItem], }; (getListHandler as jest.Mock).mockImplementation((ctx, request, res) => { return res.ok({ body: expectedResponse }); @@ -444,7 +442,6 @@ describe('schema validation', () => { it('get limited packages should return valid response', async () => { const expectedResponse: GetLimitedPackagesResponse = { items: ['test'], - response: ['test'], }; (getLimitedListHandler as jest.Mock).mockImplementation((ctx, request, res) => { return res.ok({ body: expectedResponse }); @@ -513,7 +510,6 @@ describe('schema validation', () => { metadata: { has_policies: true, }, - response: packageInfo, }; (getInfoHandler as jest.Mock).mockImplementation((ctx, request, res) => { return res.ok({ body: expectedResponse }); @@ -530,7 +526,6 @@ describe('schema validation', () => { it('update package should return valid response', async () => { const expectedResponse: UpdatePackageResponse = { item: packageInfo, - response: packageInfo, }; (updatePackageHandler as jest.Mock).mockImplementation((ctx, request, res) => { return res.ok({ body: expectedResponse }); @@ -556,13 +551,6 @@ describe('schema validation', () => { _meta: { install_source: 'registry', }, - response: [ - { - id: 'test', - type: KibanaSavedObjectType.dashboard, - originId: 'test', - }, - ], }; (installPackageFromRegistryHandler as jest.Mock).mockImplementation((ctx, request, res) => { return res.ok({ body: expectedResponse }); @@ -612,7 +600,6 @@ describe('schema validation', () => { }; const expectedResponse: BulkInstallPackagesResponse = { items: [item, { name: 'test', statusCode: 400, error: 'test' }], - response: [item], }; (bulkInstallPackagesFromRegistryHandler as jest.Mock).mockImplementation( (ctx, request, res) => { diff --git a/x-pack/plugins/fleet/server/routes/epm/index.ts b/x-pack/plugins/fleet/server/routes/epm/index.ts index 0e3c5e76eb825..283f8d6a1b0a0 100644 --- a/x-pack/plugins/fleet/server/routes/epm/index.ts +++ b/x-pack/plugins/fleet/server/routes/epm/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type { IKibanaResponse } from '@kbn/core/server'; - import { parseExperimentalConfigValue } from '../../../common/experimental_features'; import { API_VERSIONS } from '../../../common/constants'; @@ -20,32 +18,20 @@ import { } from '../../services/security'; import type { FleetAuthzRouteConfig } from '../../services/security/types'; -import type { - DeletePackageResponse, - GetInfoResponse, - InstallPackageResponse, - UpdatePackageResponse, -} from '../../../common/types'; - import { EPM_API_ROUTES } from '../../constants'; -import { splitPkgKey } from '../../services/epm/registry'; import { GetCategoriesRequestSchema, GetPackagesRequestSchema, GetInstalledPackagesRequestSchema, GetFileRequestSchema, GetInfoRequestSchema, - GetInfoRequestSchemaDeprecated, GetBulkAssetsRequestSchema, InstallPackageFromRegistryRequestSchema, - InstallPackageFromRegistryRequestSchemaDeprecated, InstallPackageByUploadRequestSchema, DeletePackageRequestSchema, - DeletePackageRequestSchemaDeprecated, BulkInstallPackagesFromRegistryRequestSchema, GetStatsRequestSchema, UpdatePackageRequestSchema, - UpdatePackageRequestSchemaDeprecated, ReauthorizeTransformRequestSchema, GetDataStreamsRequestSchema, CreateCustomIntegrationRequestSchema, @@ -648,124 +634,6 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType getBulkAssetsHandler ); - // deprecated since 8.0 - // This endpoint should be marked as internal but the router selects this endpoint over the new GET one - // For now keeping it public - router.versioned - .get({ - path: EPM_API_ROUTES.INFO_PATTERN_DEPRECATED, - fleetAuthz: (fleetAuthz: FleetAuthz): boolean => - calculateRouteAuthz( - fleetAuthz, - getRouteRequiredAuthz('get', EPM_API_ROUTES.INFO_PATTERN_DEPRECATED) - ).granted, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: GetInfoRequestSchemaDeprecated }, - }, - async (context, request, response) => { - const newRequest = { ...request, params: splitPkgKey(request.params.pkgkey) } as any; - const resp: IKibanaResponse = await getInfoHandler( - context, - newRequest, - response - ); - if (resp.payload?.item) { - // returning item as well here, because pkgVersion is optional in new GET endpoint, and if not specified, the router selects the deprecated route - return response.ok({ body: { item: resp.payload.item, response: resp.payload.item } }); - } - return resp; - } - ); - - router.versioned - .put({ - path: EPM_API_ROUTES.INFO_PATTERN_DEPRECATED, - fleetAuthz: { - integrations: { writePackageSettings: true }, - }, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: UpdatePackageRequestSchemaDeprecated }, - }, - async (context, request, response) => { - const newRequest = { ...request, params: splitPkgKey(request.params.pkgkey) } as any; - const resp: IKibanaResponse = await updatePackageHandler( - context, - newRequest, - response - ); - if (resp.payload?.item) { - return response.ok({ body: { response: resp.payload.item } }); - } - return resp; - } - ); - - // This endpoint should be marked as internal but the router selects this endpoint over the new POST - router.versioned - .post({ - path: EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN_DEPRECATED, - fleetAuthz: INSTALL_PACKAGES_AUTHZ, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: InstallPackageFromRegistryRequestSchemaDeprecated }, - }, - async (context, request, response) => { - const newRequest = { - ...request, - params: splitPkgKey(request.params.pkgkey), - query: request.query, - } as any; - const resp: IKibanaResponse = - await installPackageFromRegistryHandler(context, newRequest, response); - if (resp.payload?.items) { - return response.ok({ body: { ...resp.payload, response: resp.payload.items } }); - } - return resp; - } - ); - - router.versioned - .delete({ - path: EPM_API_ROUTES.DELETE_PATTERN_DEPRECATED, - fleetAuthz: { - integrations: { removePackages: true }, - }, - // @ts-expect-error TODO(https://github.com/elastic/kibana/issues/196095): Replace {RouteDeprecationInfo} - deprecated: true, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { request: DeletePackageRequestSchemaDeprecated }, - }, - async (context, request, response) => { - const newRequest = { ...request, params: splitPkgKey(request.params.pkgkey) } as any; - const resp: IKibanaResponse = await deletePackageHandler( - context, - newRequest, - response - ); - if (resp.payload?.items) { - return response.ok({ body: { response: resp.payload.items } }); - } - return resp; - } - ); - // Update transforms with es-secondary-authorization headers, // append authorized_by to transform's _meta, and start transforms router.versioned diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index 3892eaa951e5f..84c8a8eb9e104 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -57,14 +57,15 @@ const MAX_ASSETS_TO_DELETE = 1000; export async function removeInstallation(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; - pkgVersion: string; + pkgVersion?: string; esClient: ElasticsearchClient; force?: boolean; }): Promise { - const { savedObjectsClient, pkgName, pkgVersion, esClient } = options; + const { savedObjectsClient, pkgName, esClient } = options; const installation = await getInstallation({ savedObjectsClient, pkgName }); - if (!installation) throw new PackageRemovalError(`${pkgName} is not installed`); - + if (!installation) { + throw new PackageRemovalError(`${pkgName} is not installed`); + } const { total, items } = await packagePolicyService.list( appContextService.getInternalUserSOClientWithoutSpaceExtension(), { @@ -115,7 +116,7 @@ export async function removeInstallation(options: { // a fresh copy from the registry deletePackageCache({ name: pkgName, - version: pkgVersion, + version: installation.version, }); await removeArchiveEntries({ savedObjectsClient, refs: installation.package_assets }); diff --git a/x-pack/plugins/fleet/server/services/security/route_required_authz.ts b/x-pack/plugins/fleet/server/services/security/route_required_authz.ts index bbc1b07010fb7..37ab96b96afb7 100644 --- a/x-pack/plugins/fleet/server/services/security/route_required_authz.ts +++ b/x-pack/plugins/fleet/server/services/security/route_required_authz.ts @@ -164,7 +164,7 @@ const ROUTE_AUTHZ_REQUIREMENTS = deepFreeze p.status === 'installed') .map((p: any) => p.name) .sort(); diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/helper.ts b/x-pack/test/fleet_api_integration/apis/package_policy/helper.ts new file mode 100644 index 0000000000000..2c53248905521 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/package_policy/helper.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Agent } from 'supertest'; + +export async function getInstallationInfo(supertest: Agent, name: string, version: string) { + const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); + return res.body.item.installationInfo; +} diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts b/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts index 481f4e09c68d9..bbd55641ce916 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/input_package_create_upgrade.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; +import { getInstallationInfo } from './helper'; const PACKAGE_NAME = 'input_package_upgrade'; const START_VERSION = '1.0.0'; const UPGRADE_VERSION = '1.1.0'; @@ -33,11 +34,6 @@ export default function (providerContext: FtrProviderContext) { .expect(200); }; - const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); - return res.body.item.savedObject.attributes; - }; - const createPackagePolicyWithDataset = async ( agentPolicyId: string, dataset: string, @@ -195,13 +191,13 @@ export default function (providerContext: FtrProviderContext) { }); it('should not have created any ES assets on install', async () => { - const installation = await getInstallationSavedObject(PACKAGE_NAME, START_VERSION); + const installation = await getInstallationInfo(supertest, PACKAGE_NAME, START_VERSION); expect(installation.installed_es).to.eql([]); }); it('should create index templates and update installed_es on package policy creation', async () => { await createPackagePolicyWithDataset(agentPolicyId, 'dataset1'); - const installation = await getInstallationSavedObject(PACKAGE_NAME, START_VERSION); + const installation = await getInstallationInfo(supertest, PACKAGE_NAME, START_VERSION); expectIdArraysEqual(installation.installed_es, [ { id: 'logs-dataset1-1.0.0', type: 'ingest_pipeline' }, { id: 'logs-dataset1', type: 'index_template' }, @@ -249,7 +245,7 @@ export default function (providerContext: FtrProviderContext) { it('should create index templates and update installed_es on second package policy creation', async () => { await createPackagePolicyWithDataset(agentPolicyId, 'dataset2'); - const installation = await getInstallationSavedObject(PACKAGE_NAME, START_VERSION); + const installation = await getInstallationInfo(supertest, PACKAGE_NAME, START_VERSION); let found = 0; [ { id: 'logs-dataset2-1.0.0', type: 'ingest_pipeline' }, @@ -268,7 +264,7 @@ export default function (providerContext: FtrProviderContext) { await createFakeFleetDataStream('dataset3'); await createPackagePolicyWithDataset(agentPolicyId, 'dataset3'); - const installation = await getInstallationSavedObject(PACKAGE_NAME, START_VERSION); + const installation = await getInstallationInfo(supertest, PACKAGE_NAME, START_VERSION); let found = 0; [ { id: 'logs-dataset3-1.0.0', type: 'ingest_pipeline' }, diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/input_package_rollback.ts b/x-pack/test/fleet_api_integration/apis/package_policy/input_package_rollback.ts index 2ea9c64ee2507..7ccd2fad8bdde 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/input_package_rollback.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/input_package_rollback.ts @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; +import { getInstallationInfo } from './helper'; const PACKAGE_NAME = 'input_package_upgrade'; const START_VERSION = '1.0.0'; @@ -31,11 +32,6 @@ export default function (providerContext: FtrProviderContext) { .expect(200); }; - const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); - return res.body.item.savedObject.attributes; - }; - const getPackage = async (name: string, version: string) => { const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); return res.body.item; @@ -131,7 +127,7 @@ export default function (providerContext: FtrProviderContext) { await installPackage(PACKAGE_NAME, START_VERSION); await createPackagePolicyWithDataset(agentPolicyId, 'test*', 400); - const installation = await getInstallationSavedObject(PACKAGE_NAME, START_VERSION); + const installation = await getInstallationInfo(supertest, PACKAGE_NAME, START_VERSION); expectIdArraysEqual(installation.installed_es, []); await uninstallPackage(PACKAGE_NAME, START_VERSION); diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts index 273f051dfcec6..619ddeb0544e7 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts @@ -15,6 +15,7 @@ import { enableSecrets, } from '../../helpers'; import { testUsers } from '../test_users'; +import { getInstallationInfo } from './helper'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; @@ -28,11 +29,6 @@ export default function (providerContext: FtrProviderContext) { expect(sortBy(arr1, 'id')).to.eql(sortBy(arr2, 'id')); }; - const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); - return res.body.item.savedObject.attributes; - }; - const getPackagePolicyById = async (id: string) => { const { body } = await supertest.get(`/api/fleet/package_policies/${id}`); return body; @@ -935,7 +931,7 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - const installation = await getInstallationSavedObject('integration_to_input', '2.0.0'); + const installation = await getInstallationInfo(supertest, 'integration_to_input', '2.0.0'); expectIdArraysEqual(installation.installed_es, [ // assets from version 1.0.0 diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts b/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts index 021eebcdcc0c1..e0fbddb578a91 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/upgrade.ts @@ -12,6 +12,7 @@ import { import { sortBy } from 'lodash'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; +import { getInstallationInfo } from './helper'; const expectIdArraysEqual = (arr1: any[], arr2: any[]) => { expect(sortBy(arr1, 'id')).to.eql(sortBy(arr2, 'id')); @@ -36,11 +37,6 @@ export default function (providerContext: FtrProviderContext) { }); } - const getInstallationSavedObject = async (name: string, version: string) => { - const res = await supertest.get(`/api/fleet/epm/packages/${name}/${version}`).expect(200); - return res.body.item.savedObject.attributes; - }; - const getComponentTemplate = async (name: string) => { try { const { component_templates: templates } = await es.cluster.getComponentTemplate({ name }); @@ -1358,7 +1354,11 @@ export default function (providerContext: FtrProviderContext) { }) .expect(200); - const installation = await getInstallationSavedObject('integration_to_input', '3.0.0'); + const installation = await getInstallationInfo( + supertest, + 'integration_to_input', + '3.0.0' + ); expectIdArraysEqual(installation.installed_es, expectedAssets); const expectedComponentTemplates = expectedAssets.filter( diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 5035b6844b9c3..915060b152f24 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -594,12 +594,12 @@ export function MachineLearningTestResourcesProvider( await retry.tryForTime(10 * 1000, async () => { const { body, status } = await supertest - .get(`/api/fleet/epm/packages?experimental=true`) + .get(`/api/fleet/epm/packages?prerelease=true`) .set(getCommonRequestHeader(`${API_VERSIONS.public.v1}`)); mlApi.assertResponseStatusCode(200, status, body); packageVersion = - body.response.find( + body.items.find( ({ name, version }: { name: string; version: string }) => name === packageName && version )?.version ?? ''; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts index e53e24f98de3b..cea8363d9085c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts @@ -21,9 +21,9 @@ export async function deleteEndpointFleetPackage(supertest: SuperTest.Agent) { .set('elastic-api-version', '2023-10-31') .send(); - if (resp.status === 200 && resp.body.response.status === 'installed') { + if (resp.status === 200 && resp.body.item.status === 'installed') { await supertest - .delete(epmRouteService.getRemovePath(ENDPOINT_PACKAGE_NAME, resp.body.response.version)) + .delete(epmRouteService.getRemovePath(ENDPOINT_PACKAGE_NAME, resp.body.item.version)) .set('kbn-xsrf', 'true') .send({ force: true }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts index 930c9d39757f4..084d59c55df0d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts @@ -21,11 +21,9 @@ export async function deletePrebuiltRulesFleetPackage(supertest: SuperTest.Agent .set('elastic-api-version', '2023-10-31') .send(); - if (resp.status === 200 && resp.body.response.status === 'installed') { + if (resp.status === 200 && resp.body.item.status === 'installed') { await supertest - .delete( - epmRouteService.getRemovePath(PREBUILT_RULES_PACKAGE_NAME, resp.body.response.version) - ) + .delete(epmRouteService.getRemovePath(PREBUILT_RULES_PACKAGE_NAME, resp.body.item.version)) .set('kbn-xsrf', 'true') .send({ force: true }); } From cdb9ef37e67d23c1a5bec27789a4d0398a4f2250 Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Wed, 6 Nov 2024 14:48:04 +0100 Subject: [PATCH 14/53] [Search] [Playground] Update use source indices fields hook tests (#198393) ## Summary Summarize your PR. If it involves visual changes include a screenshot or gif. ### Checklist Delete any items that are not applicable to this PR. - [ ] 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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] 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 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) - [ ] This will appear in the **Release Notes** and follow the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../hooks/use_source_indices_fields.test.tsx | 182 ++++++------------ 1 file changed, 60 insertions(+), 122 deletions(-) diff --git a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx index b31512177d3cb..4adf3d18ea92b 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx +++ b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx @@ -6,144 +6,82 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useKibana } from './use_kibana'; -import { PlaygroundProvider } from '../providers/playground_provider'; -import React from 'react'; -import * as ReactHookForm from 'react-hook-form'; - -jest.mock('./use_kibana', () => ({ - useKibana: jest.fn(), -})); -jest.mock('react-router-dom-v5-compat', () => ({ - useSearchParams: jest.fn(() => [{ get: jest.fn() }]), -})); - -let formHookSpy: jest.SpyInstance; - +import { useUsageTracker } from './use_usage_tracker'; +import { useController } from 'react-hook-form'; +import { useIndicesFields } from './use_indices_fields'; +import { AnalyticsEvents } from '../analytics/constants'; import { useSourceIndicesFields } from './use_source_indices_field'; -import { IndicesQuerySourceFields } from '../types'; -// Failing: See https://github.com/elastic/kibana/issues/188840 -describe.skip('useSourceIndicesFields Hook', () => { - let postMock: jest.Mock; - - beforeEach(() => { - // Playground Provider has the formProvider which - // persists the form state into local storage - // We need to clear the local storage before each test - localStorage.clear(); - }); +jest.mock('./use_usage_tracker'); +jest.mock('react-hook-form'); +jest.mock('./use_indices_fields'); - const wrapper = ({ children }: { children: React.ReactNode }) => ( - {children} - ); +describe('useSourceIndicesFields', () => { + const mockUsageTracker = { + count: jest.fn(), + }; + const mockOnChange = jest.fn(); + const mockFields = ['field1', 'field2']; + const mockSelectedIndices = ['index1', 'index2']; beforeEach(() => { - formHookSpy = jest.spyOn(ReactHookForm, 'useForm'); - const querySourceFields: IndicesQuerySourceFields = { - newIndex: { - elser_query_fields: [ - { - field: 'field1', - model_id: 'model1', - indices: ['newIndex'], - sparse_vector: true, - }, - ], - dense_vector_query_fields: [], - bm25_query_fields: [], - source_fields: ['field1'], - skipped_fields: 0, - semantic_fields: [], - }, - }; - - postMock = jest.fn().mockResolvedValue(querySourceFields); - (useKibana as jest.Mock).mockImplementation(() => ({ - services: { - http: { - post: postMock, - get: jest.fn(() => { - return []; - }), - }, - }, - })); - }); - - afterEach(() => { jest.clearAllMocks(); + (useUsageTracker as jest.Mock).mockReturnValue(mockUsageTracker); + (useController as jest.Mock).mockReturnValue({ + field: { value: mockSelectedIndices, onChange: mockOnChange }, + }); + (useIndicesFields as jest.Mock).mockReturnValue({ + fields: mockFields, + isLoading: false, + }); }); - it('should handle addIndex correctly changing indices', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSourceIndicesFields(), { wrapper }); - const { getValues } = formHookSpy.mock.results[0].value; + it('should initialize correctly', () => { + const { result } = renderHook(() => useSourceIndicesFields()); + + expect(result.current.indices).toEqual(mockSelectedIndices); + expect(result.current.fields).toEqual(mockFields); + expect(result.current.isFieldsLoading).toBe(false); + }); + it('should add an index', () => { + const { result } = renderHook(() => useSourceIndicesFields()); act(() => { - expect(result.current.indices).toEqual([]); - expect(getValues()).toMatchInlineSnapshot(` - Object { - "doc_size": 3, - "elasticsearch_query": Object { - "retriever": Object { - "standard": Object { - "query": Object { - "match_all": Object {}, - }, - }, - }, - }, - "indices": Array [], - "prompt": "You are an assistant for question-answering tasks.", - "query_fields": Object {}, - "source_fields": Object {}, - "summarization_model": undefined, - } - `); result.current.addIndex('newIndex'); }); - await act(async () => { - await waitForNextUpdate(); - expect(result.current.indices).toEqual(['newIndex']); + expect(mockOnChange).toHaveBeenCalledWith([...mockSelectedIndices, 'newIndex']); + expect(mockUsageTracker.count).toHaveBeenCalledWith( + AnalyticsEvents.sourceIndexUpdated, + mockSelectedIndices.length + 1 + ); + }); + + it('should remove an index', () => { + const { result } = renderHook(() => useSourceIndicesFields()); + act(() => { + result.current.removeIndex('index1'); }); - expect(postMock).toHaveBeenCalled(); + expect(mockOnChange).toHaveBeenCalledWith(['index2']); + expect(mockUsageTracker.count).toHaveBeenCalledWith( + AnalyticsEvents.sourceIndexUpdated, + mockSelectedIndices.length - 1 + ); + }); + + it('should set indices', () => { + const { result } = renderHook(() => useSourceIndicesFields()); + const newIndices = ['index3', 'index4']; - await act(async () => { - expect(getValues()).toMatchInlineSnapshot(` - Object { - "doc_size": 3, - "elasticsearch_query": Object { - "retriever": Object { - "standard": Object { - "query": Object { - "sparse_vector": Object { - "field": "field1", - "inference_id": "model1", - "query": "{query}", - }, - }, - }, - }, - }, - "indices": Array [ - "newIndex", - ], - "prompt": "You are an assistant for question-answering tasks.", - "query_fields": Object { - "newIndex": Array [ - "field1", - ], - }, - "source_fields": Object { - "newIndex": Array [ - "field1", - ], - }, - "summarization_model": undefined, - } - `); + act(() => { + result.current.setIndices(newIndices); }); + + expect(mockOnChange).toHaveBeenCalledWith(newIndices); + expect(mockUsageTracker.count).toHaveBeenCalledWith( + AnalyticsEvents.sourceIndexUpdated, + newIndices.length + ); }); }); From de46e7f0739eb14138655e204d4be77d8e24ff37 Mon Sep 17 00:00:00 2001 From: Jordan <51442161+JordanSh@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:52:30 +0200 Subject: [PATCH 15/53] [Cloud Security] Fixes SVG flickering and adding insight telemetry (#198812) --- .../common/utils/ui_metrics.ts | 17 ++++++++++++++- .../empty_states_illustration_container.tsx | 21 +++++++++++++++++++ .../no_findings_states/no_findings_states.tsx | 21 ++++++++++++------- .../components/no_vulnerabilities_states.tsx | 21 ++++++++++++------- .../left/components/host_details.tsx | 2 ++ .../left/components/user_details.tsx | 1 + .../right/components/host_entity_overview.tsx | 2 ++ .../right/components/user_entity_overview.tsx | 1 + .../components/misconfiguration_insight.tsx | 20 +++++++++++++++++- .../components/vulnerabilities_insight.tsx | 20 +++++++++++++++++- 10 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/components/empty_states_illustration_container.tsx diff --git a/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts b/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts index 252252b08e976..c7baeb47bc214 100644 --- a/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts +++ b/x-pack/packages/kbn-cloud-security-posture/common/utils/ui_metrics.ts @@ -10,6 +10,15 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; export const APP_NAME = 'cloud-security'; +export const MISCONFIGURATION_INSIGHT = 'misconfiguration-insight'; +export const VULNERABILITIES_INSIGHT = 'vulnerabilities-insight'; +export const MISCONFIGURATION_INSIGHT_HOST_DETAILS = `${MISCONFIGURATION_INSIGHT}-host-details`; +export const MISCONFIGURATION_INSIGHT_USER_DETAILS = `${MISCONFIGURATION_INSIGHT}-user-details`; +export const MISCONFIGURATION_INSIGHT_HOST_ENTITY_OVERVIEW = `${MISCONFIGURATION_INSIGHT}-host-entity-overview`; +export const MISCONFIGURATION_INSIGHT_USER_ENTITY_OVERVIEW = `${MISCONFIGURATION_INSIGHT}-user-entity-overview`; +export const VULNERABILITIES_INSIGHT_HOST_DETAILS = `${VULNERABILITIES_INSIGHT}-host-details`; +export const VULNERABILITIES_INSIGHT_HOST_ENTITY_OVERVIEW = `${VULNERABILITIES_INSIGHT}-host-entity-overview`; + export const ENTITY_FLYOUT_WITH_MISCONFIGURATION_VISIT = 'entity-flyout-with-misconfiguration-visits'; export const ENTITY_FLYOUT_WITH_VULNERABILITY_PREVIEW = @@ -41,7 +50,13 @@ type CloudSecurityUiCounters = | typeof CREATE_DETECTION_RULE_FROM_FLYOUT | typeof CREATE_DETECTION_FROM_TABLE_ROW_ACTION | typeof GROUP_BY_CLICK - | typeof CHANGE_RULE_STATE; + | typeof CHANGE_RULE_STATE + | typeof MISCONFIGURATION_INSIGHT_HOST_DETAILS + | typeof MISCONFIGURATION_INSIGHT_USER_DETAILS + | typeof MISCONFIGURATION_INSIGHT_HOST_ENTITY_OVERVIEW + | typeof MISCONFIGURATION_INSIGHT_USER_ENTITY_OVERVIEW + | typeof VULNERABILITIES_INSIGHT_HOST_DETAILS + | typeof VULNERABILITIES_INSIGHT_HOST_ENTITY_OVERVIEW; export class UiMetricService { private usageCollection: UsageCollectionSetup | undefined; diff --git a/x-pack/plugins/cloud_security_posture/public/components/empty_states_illustration_container.tsx b/x-pack/plugins/cloud_security_posture/public/components/empty_states_illustration_container.tsx new file mode 100644 index 0000000000000..3ae4b64b1c848 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/empty_states_illustration_container.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +// dimensions of the SVGs used in the empty states illustrations +// e.g. x-pack/plugins/cloud_security_posture/public/assets/illustrations/clouds.svg +const SVG_HEIGHT = 209; +const SVG_WIDTH = 376; + +/** + * A container component that maintains a fixed size for child elements. + * used for displaying the empty state illustrations and prevent flickering while the SVGs are loading. + */ +export const EmptyStatesIllustrationContainer: React.FC<{ children: React.ReactNode }> = ({ + children, +}) =>
{children}
; diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx index 5a8db618d5495..a475d35cd6885 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx @@ -26,6 +26,7 @@ import type { IndexDetails, CspStatusCode } from '@kbn/cloud-security-posture-co import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; import { useLocation } from 'react-router-dom'; import { findingsNavigation } from '@kbn/cloud-security-posture'; +import { EmptyStatesIllustrationContainer } from '../empty_states_illustration_container'; import { useAdd3PIntegrationRoute } from '../../common/api/use_wiz_integration_route'; import { FullSizeCenteredPage } from '../full_size_centered_page'; import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies'; @@ -191,7 +192,11 @@ const EmptySecurityFindingsPrompt = () => { } + icon={ + + + + } title={

{ style={{ padding: euiTheme.size.l }} data-test-subj={THIRD_PARTY_INTEGRATIONS_NO_MISCONFIGURATIONS_FINDINGS_PROMPT} icon={ - + + + } title={

diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx index 20438aa341ad6..4e6b487f73cbc 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_vulnerabilities_states.tsx @@ -25,6 +25,7 @@ import type { IndexDetails } from '@kbn/cloud-security-posture-common'; import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; import { useLocation } from 'react-router-dom'; import { findingsNavigation } from '@kbn/cloud-security-posture'; +import { EmptyStatesIllustrationContainer } from './empty_states_illustration_container'; import { VULN_MGMT_POLICY_TEMPLATE } from '../../common/constants'; import { FullSizeCenteredPage } from './full_size_centered_page'; import { CloudPosturePage } from './cloud_posture_page'; @@ -84,7 +85,11 @@ const CnvmIntegrationNotInstalledEmptyPrompt = ({ } + icon={ + + + + } title={

+ + + } title={

diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx index f5a1e112afa80..c315e991d9f06 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/host_details.tsx @@ -359,11 +359,13 @@ export const HostDetails: React.FC = ({ hostName, timestamp, s name={hostName} direction="column" data-test-subj={HOST_DETAILS_MISCONFIGURATIONS_TEST_ID} + telemetrySuffix={'host-details'} /> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx index ed406d3b8c679..2f98c641b5954 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/user_details.tsx @@ -359,6 +359,7 @@ export const UserDetails: React.FC = ({ userName, timestamp, s name={userName} direction="column" data-test-subj={USER_DETAILS_MISCONFIGURATIONS_TEST_ID} + telemetrySuffix={'user-details'} /> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx index 90405286b004c..9b60eefbb5f61 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/host_entity_overview.tsx @@ -285,10 +285,12 @@ export const HostEntityOverview: React.FC = ({ hostName fieldName={'host.name'} name={hostName} data-test-subj={ENTITIES_HOST_OVERVIEW_MISCONFIGURATIONS_TEST_ID} + telemetrySuffix={'host-entity-overview'} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx index 0019228d656cd..1008f6139cd67 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/user_entity_overview.tsx @@ -283,6 +283,7 @@ export const UserEntityOverview: React.FC = ({ userName fieldName={'user.name'} name={userName} data-test-subj={ENTITIES_USER_OVERVIEW_MISCONFIGURATIONS_TEST_ID} + telemetrySuffix={'user-entity-overview'} /> ); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx index 961fa1d5f3a45..e7ebb371fb020 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/misconfiguration_insight.tsx @@ -5,12 +5,17 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/css'; import { useMisconfigurationPreview } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_preview'; import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; +import { + MISCONFIGURATION_INSIGHT, + uiMetricService, +} from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { METRIC_TYPE } from '@kbn/analytics'; import { InsightDistributionBar } from './insight_distribution_bar'; import { getFindingsStats } from '../../../../cloud_security_posture/components/misconfiguration/misconfiguration_preview'; import { FormattedCount } from '../../../../common/components/formatted_number'; @@ -34,6 +39,10 @@ interface MisconfigurationsInsightProps { * The data-test-subj to use for the component */ ['data-test-subj']?: string; + /** + * used to track the instance of this component, prefer kebab-case + */ + telemetrySuffix?: string; } /* @@ -44,6 +53,7 @@ export const MisconfigurationsInsight: React.FC = fieldName, direction, 'data-test-subj': dataTestSubj, + telemetrySuffix, }) => { const { scopeId, isPreview } = useDocumentDetailsContext(); const { euiTheme } = useEuiTheme(); @@ -54,6 +64,14 @@ export const MisconfigurationsInsight: React.FC = pageSize: 1, }); + useEffect(() => { + uiMetricService.trackUiMetric( + METRIC_TYPE.COUNT, + `${MISCONFIGURATION_INSIGHT}-${telemetrySuffix}` + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const passedFindings = data?.count.passed || 0; const failedFindings = data?.count.failed || 0; const totalFindings = useMemo( diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx index c675c0a0e079b..1dab4660194b9 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/components/vulnerabilities_insight.tsx @@ -5,13 +5,18 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { EuiFlexItem, type EuiFlexGroupProps, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/css'; import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; import { buildEntityFlyoutPreviewQuery } from '@kbn/cloud-security-posture-common'; import { getVulnerabilityStats, hasVulnerabilitiesData } from '@kbn/cloud-security-posture'; +import { + uiMetricService, + VULNERABILITIES_INSIGHT, +} from '@kbn/cloud-security-posture-common/utils/ui_metrics'; +import { METRIC_TYPE } from '@kbn/analytics'; import { InsightDistributionBar } from './insight_distribution_bar'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { PreviewLink } from '../../../shared/components/preview_link'; @@ -30,6 +35,10 @@ interface VulnerabilitiesInsightProps { * The data-test-subj to use for the component */ ['data-test-subj']?: string; + /** + * used to track the instance of this component, prefer kebab-case + */ + telemetrySuffix?: string; } /* @@ -39,6 +48,7 @@ export const VulnerabilitiesInsight: React.FC = ({ hostName, direction, 'data-test-subj': dataTestSubj, + telemetrySuffix, }) => { const { scopeId, isPreview } = useDocumentDetailsContext(); const { euiTheme } = useEuiTheme(); @@ -49,6 +59,14 @@ export const VulnerabilitiesInsight: React.FC = ({ pageSize: 1, }); + useEffect(() => { + uiMetricService.trackUiMetric( + METRIC_TYPE.COUNT, + `${VULNERABILITIES_INSIGHT}-${telemetrySuffix}` + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {}; const totalVulnerabilities = useMemo( () => CRITICAL + HIGH + MEDIUM + LOW + NONE, From deeb9fe32af717a883727aed7d83c6106d8d839f Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Wed, 6 Nov 2024 16:06:39 +0200 Subject: [PATCH 16/53] fix(security, features): do not expose UI capabilities of the deprecated features (#198656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR ensures that we don’t expose UI capabilities for deprecated features since they’re unnecessary, and the code should rely on the UI capabilities of the replacement features instead. Additionally, this PR transforms the `disabledFeatures` property of Space objects returned from our programmatic and HTTP APIs to replace any deprecated feature IDs with the IDs of their replacement features, ensuring that feature visibility toggles work for deprecated features as well. ## How to test 1. Run Kibana FTR server with the following config (registers test deprecated features): ```shell node scripts/functional_tests_server.js --config x-pack/test/security_api_integration/features.config.ts ``` 2. Once server is up and running create Space with the `case_1_feature_a` **deprecated** feature disabled: ```shell curl 'http://localhost:5620/api/spaces/space' -u elastic:changeme \ -X POST -H 'Content-Type: application/json' -H 'kbn-version: 9.0.0' \ --data-raw '{"name":"space-alpha","id":"space-alpha","initials":"s","color":"#D6BF57","disabledFeatures":["case_1_feature_a"],"imageUrl":""}' ``` 3. Log in to Kibana and [navigate to a Space `space-alpha`](http://localhost:5620/app/management/kibana/spaces/edit/space-alpha) you've just created. Observe that deprecated `Case #1 feature A` (`case_1_feature_a`) isn't displayed, and instead you should see that replaces deprecated one - `Case #1 feature B` (`case_1_feature_b`): ![Screen Shot 2024-11-01 at 17 40 59](https://github.com/user-attachments/assets/5b91e71c-7d46-4ff1-bf73-d148622e8ec4) Co-authored-by: Elastic Machine --- x-pack/plugins/features/server/mocks.ts | 4 +- x-pack/plugins/features/server/plugin.ts | 3 +- .../authorization_service.test.ts | 10 +- x-pack/plugins/security/server/plugin.test.ts | 4 +- .../capabilities_switcher.test.ts | 28 ++++- .../capabilities/capabilities_switcher.ts | 2 +- .../utils/space_solution_disabled_features.ts | 1 + .../server/routes/api/external/post.test.ts | 6 +- .../server/routes/api/external/put.test.ts | 6 +- .../spaces_client/spaces_client.test.ts | 48 ++++++++ .../server/spaces_client/spaces_client.ts | 59 ++++++++- .../plugins/features_provider/server/index.ts | 30 ++++- .../features_provider/server/init_routes.ts | 25 ++++ .../tests/features/deprecated_features.ts | 113 ++++++++++++++++-- x-pack/test/ui_capabilities/common/config.ts | 4 + 15 files changed, 301 insertions(+), 42 deletions(-) create mode 100644 x-pack/test/security_api_integration/plugins/features_provider/server/init_routes.ts diff --git a/x-pack/plugins/features/server/mocks.ts b/x-pack/plugins/features/server/mocks.ts index bb2292a45377f..15339b068e7e8 100644 --- a/x-pack/plugins/features/server/mocks.ts +++ b/x-pack/plugins/features/server/mocks.ts @@ -25,8 +25,8 @@ const createSetup = (): jest.Mocked => { const createStart = (): jest.Mocked => { return { - getKibanaFeatures: jest.fn(), - getElasticsearchFeatures: jest.fn(), + getKibanaFeatures: jest.fn().mockReturnValue([]), + getElasticsearchFeatures: jest.fn().mockReturnValue([]), }; }; diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts index 15888358bb773..9f6cae36f6aee 100644 --- a/x-pack/plugins/features/server/plugin.ts +++ b/x-pack/plugins/features/server/plugin.ts @@ -138,7 +138,8 @@ export class FeaturesPlugin this.featureRegistry.validateFeatures(); this.capabilities = uiCapabilitiesForFeatures( - this.featureRegistry.getAllKibanaFeatures(), + // Don't expose capabilities of the deprecated features. + this.featureRegistry.getAllKibanaFeatures({ omitDeprecated: true }), this.featureRegistry.getAllElasticsearchFeatures() ); diff --git a/x-pack/plugins/security/server/authorization/authorization_service.test.ts b/x-pack/plugins/security/server/authorization/authorization_service.test.ts index 275a6d2643f24..de3646166d8f9 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.test.ts +++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts @@ -145,12 +145,9 @@ describe('#start', () => { customBranding: mockCoreSetup.customBranding, }); - const featuresStart = featuresPluginMock.createStart(); - featuresStart.getKibanaFeatures.mockReturnValue([]); - authorizationService.start({ clusterClient: mockClusterClient, - features: featuresStart, + features: featuresPluginMock.createStart(), online$: statusSubject.asObservable(), }); @@ -217,12 +214,9 @@ it('#stop unsubscribes from license and ES updates.', async () => { customBranding: mockCoreSetup.customBranding, }); - const featuresStart = featuresPluginMock.createStart(); - featuresStart.getKibanaFeatures.mockReturnValue([]); - authorizationService.start({ clusterClient: mockClusterClient, - features: featuresStart, + features: featuresPluginMock.createStart(), online$: statusSubject.asObservable(), }); diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 37c6e22a07fab..4b9479f51a0f3 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -64,10 +64,8 @@ describe('Security Plugin', () => { mockCoreStart = coreMock.createStart(); - const mockFeaturesStart = featuresPluginMock.createStart(); - mockFeaturesStart.getKibanaFeatures.mockReturnValue([]); mockStartDependencies = { - features: mockFeaturesStart, + features: featuresPluginMock.createStart(), licensing: licensingMock.createStart(), taskManager: taskManagerMock.createStart(), }; diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts index d48095638babf..688f8297271a3 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts @@ -66,7 +66,7 @@ const features = [ category: { id: 'securitySolution' }, }, { - // feature 4 intentionally delcares the same items as feature 3 + // feature 4 intentionally declares the same items as feature 3 id: 'feature_4', name: 'Feature 4', app: ['feature3', 'feature3_app'], @@ -87,6 +87,32 @@ const features = [ }, category: { id: 'observability' }, }, + { + deprecated: { notice: 'It was a mistake.' }, + id: 'deprecated_feature', + name: 'Deprecated Feature', + // Expose the same `app` and `catalogue` entries as `feature_2` to make sure they are disabled + // when `feature_2` is disabled even if the deprecated feature isn't explicitly disabled. + app: ['feature2'], + catalogue: ['feature2Entry'], + category: { id: 'deprecated', label: 'deprecated' }, + privileges: { + all: { + savedObject: { all: [], read: [] }, + ui: ['ui_deprecated_all'], + app: ['feature2'], + catalogue: ['feature2Entry'], + replacedBy: [{ feature: 'feature_2', privileges: ['all'] }], + }, + read: { + savedObject: { all: [], read: [] }, + ui: ['ui_deprecated_read'], + app: ['feature2'], + catalogue: ['feature2Entry'], + replacedBy: [{ feature: 'feature_2', privileges: ['all'] }], + }, + }, + }, ] as unknown as KibanaFeature[]; const buildCapabilities = () => diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts index 90ee85fece486..41d5dcdf2cb14 100644 --- a/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts +++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts @@ -72,7 +72,7 @@ function toggleDisabledFeatures( (acc, feature) => { if (disabledFeatureKeys.includes(feature.id)) { acc.disabledFeatures.push(feature); - } else { + } else if (!feature.deprecated) { acc.enabledFeatures.push(feature); } return acc; diff --git a/x-pack/plugins/spaces/server/lib/utils/space_solution_disabled_features.ts b/x-pack/plugins/spaces/server/lib/utils/space_solution_disabled_features.ts index 066166e7e87dd..6d30645325535 100644 --- a/x-pack/plugins/spaces/server/lib/utils/space_solution_disabled_features.ts +++ b/x-pack/plugins/spaces/server/lib/utils/space_solution_disabled_features.ts @@ -39,6 +39,7 @@ const enabledFeaturesPerSolution: Record = { * This function takes the current space's disabled features and the space solution and returns * the updated array of disabled features. * + * @param features The list of all Kibana registered features. * @param spaceDisabledFeatures The current space's disabled features * @param spaceSolution The current space's solution (es, oblt, security or classic) * @returns The updated array of disabled features diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index 984d684762159..88c846b77eb53 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -56,13 +56,9 @@ describe('Spaces Public API', () => { basePath: httpService.basePath, }); - const featuresPluginMockStart = featuresPluginMock.createStart(); - - featuresPluginMockStart.getKibanaFeatures.mockReturnValue([]); - const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract()); - const clientServiceStart = clientService.start(coreStart, featuresPluginMockStart); + const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart()); const spacesServiceStart = service.start({ basePath: coreStart.http.basePath, diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index 8aa71d30fc4bb..cf2e9981fd024 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -56,13 +56,9 @@ describe('PUT /api/spaces/space', () => { basePath: httpService.basePath, }); - const featuresPluginMockStart = featuresPluginMock.createStart(); - - featuresPluginMockStart.getKibanaFeatures.mockReturnValue([]); - const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract()); - const clientServiceStart = clientService.start(coreStart, featuresPluginMockStart); + const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart()); const spacesServiceStart = service.start({ basePath: coreStart.http.basePath, diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index 364afdcaba66a..4b7c1de0b3fcb 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -55,6 +55,37 @@ const features = [ catalogue: ['feature3Entry'], category: { id: 'securitySolution' }, }, + { + deprecated: { notice: 'It was a mistake.' }, + id: 'feature_4_deprecated', + name: 'Deprecated Feature', + app: ['feature2', 'feature3'], + catalogue: ['feature2Entry', 'feature3Entry'], + category: { id: 'deprecated', label: 'deprecated' }, + scope: ['spaces', 'security'], + privileges: { + all: { + savedObject: { all: [], read: [] }, + ui: [], + app: ['feature2', 'feature3'], + catalogue: ['feature2Entry', 'feature3Entry'], + replacedBy: [ + { feature: 'feature_2', privileges: ['all'] }, + { feature: 'feature_3', privileges: ['all'] }, + ], + }, + read: { + savedObject: { all: [], read: [] }, + ui: [], + app: ['feature2', 'feature3'], + catalogue: ['feature2Entry', 'feature3Entry'], + replacedBy: [ + { feature: 'feature_2', privileges: ['read'] }, + { feature: 'feature_3', privileges: ['read'] }, + ], + }, + }, + }, ] as unknown as KibanaFeature[]; const featuresStart = featuresPluginMock.createStart(); @@ -103,6 +134,17 @@ describe('#getAll', () => { bar: 'baz-bar', // an extra attribute that will be ignored during conversion }, }, + { + // alpha only has deprecated disabled features + id: 'alpha', + type: 'space', + references: [], + attributes: { + name: 'alpha-name', + description: 'alpha-description', + disabledFeatures: ['feature_1', 'feature_4_deprecated'], + }, + }, ]; const expectedSpaces: Space[] = [ @@ -130,6 +172,12 @@ describe('#getAll', () => { description: 'baz-description', disabledFeatures: [], }, + { + id: 'alpha', + name: 'alpha-name', + description: 'alpha-description', + disabledFeatures: ['feature_1', 'feature_2', 'feature_3'], + }, ]; test(`finds spaces using callWithRequestRepository`, async () => { diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts index 5d7ae1159f5ea..66728636f9752 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts @@ -14,6 +14,7 @@ import type { SavedObject, } from '@kbn/core/server'; import type { LegacyUrlAliasTarget } from '@kbn/core-saved-objects-common'; +import type { KibanaFeature } from '@kbn/features-plugin/common'; import { KibanaFeatureScope } from '@kbn/features-plugin/common'; import type { FeaturesPluginStart } from '@kbn/features-plugin/server'; @@ -84,7 +85,13 @@ export interface ISpacesClient { * Client for interacting with spaces. */ export class SpacesClient implements ISpacesClient { - private isServerless = false; + private readonly isServerless: boolean; + + /** + * A map of deprecated feature IDs to the feature IDs that replace them used to transform the disabled features + * of a space to make sure they only reference non-deprecated features. + */ + private readonly deprecatedFeaturesReferences: Map>; constructor( private readonly debugLogger: (message: string) => void, @@ -95,6 +102,9 @@ export class SpacesClient implements ISpacesClient { private readonly features: FeaturesPluginStart ) { this.isServerless = this.buildFlavour === 'serverless'; + this.deprecatedFeaturesReferences = this.collectDeprecatedFeaturesReferences( + features.getKibanaFeatures() + ); } public async getAll(options: v1.GetAllSpacesOptions = {}): Promise { @@ -247,6 +257,8 @@ export class SpacesClient implements ISpacesClient { }; private transformSavedObjectToSpace = (savedObject: SavedObject): v1.Space => { + // Solution isn't supported in the serverless offering. + const solution = !this.isServerless ? savedObject.attributes.solution : undefined; return { id: savedObject.id, name: savedObject.attributes.name ?? '', @@ -256,11 +268,13 @@ export class SpacesClient implements ISpacesClient { imageUrl: savedObject.attributes.imageUrl, disabledFeatures: withSpaceSolutionDisabledFeatures( this.features.getKibanaFeatures(), - savedObject.attributes.disabledFeatures ?? [], - !this.isServerless ? savedObject.attributes.solution : undefined + savedObject.attributes.disabledFeatures?.flatMap((featureId: string) => + Array.from(this.deprecatedFeaturesReferences.get(featureId) ?? [featureId]) + ) ?? [], + solution ), _reserved: savedObject.attributes._reserved, - ...(!this.isServerless ? { solution: savedObject.attributes.solution } : {}), + ...(solution ? { solution } : {}), } as v1.Space; }; @@ -275,4 +289,41 @@ export class SpacesClient implements ISpacesClient { ...(!this.isServerless && space.solution ? { solution: space.solution } : {}), }; }; + + /** + * Collects a map of all deprecated feature IDs and the feature IDs that replace them. + * @param features A list of all available Kibana features including deprecated ones. + */ + private collectDeprecatedFeaturesReferences(features: KibanaFeature[]) { + const deprecatedFeatureReferences = new Map(); + for (const feature of features) { + if (!feature.deprecated || !feature.scope?.includes(KibanaFeatureScope.Spaces)) { + continue; + } + + // Collect all feature privileges including the ones provided by sub-features, if any. + const allPrivileges = Object.values(feature.privileges ?? {}).concat( + feature.subFeatures?.flatMap((subFeature) => + subFeature.privilegeGroups.flatMap(({ privileges }) => privileges) + ) ?? [] + ); + + // Collect all features IDs that are referenced by the deprecated feature privileges. + const referencedFeaturesIds = new Set(); + for (const privilege of allPrivileges) { + const replacedBy = privilege.replacedBy + ? 'default' in privilege.replacedBy + ? privilege.replacedBy.default.concat(privilege.replacedBy.minimal) + : privilege.replacedBy + : []; + for (const privilegeReference of replacedBy) { + referencedFeaturesIds.add(privilegeReference.feature); + } + } + + deprecatedFeatureReferences.set(feature.id, referencedFeaturesIds); + } + + return deprecatedFeatureReferences; + } } diff --git a/x-pack/test/security_api_integration/plugins/features_provider/server/index.ts b/x-pack/test/security_api_integration/plugins/features_provider/server/index.ts index 646fe327a0015..61100babefea7 100644 --- a/x-pack/test/security_api_integration/plugins/features_provider/server/index.ts +++ b/x-pack/test/security_api_integration/plugins/features_provider/server/index.ts @@ -9,8 +9,11 @@ import type { PluginSetupContract as AlertingPluginsSetup } from '@kbn/alerting- import { schema } from '@kbn/config-schema'; import type { CoreSetup, Plugin, PluginInitializer } from '@kbn/core/server'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; +import { KibanaFeatureScope } from '@kbn/features-plugin/common'; import type { FeaturesPluginSetup, FeaturesPluginStart } from '@kbn/features-plugin/server'; +import { initRoutes } from './init_routes'; + export interface PluginSetupDependencies { features: FeaturesPluginSetup; alerting: AlertingPluginsSetup; @@ -23,7 +26,7 @@ export interface PluginStartDependencies { export const plugin: PluginInitializer = async (): Promise< Plugin > => ({ - setup: (_: CoreSetup, deps: PluginSetupDependencies) => { + setup: (core: CoreSetup, deps: PluginSetupDependencies) => { // Case #1: feature A needs to be renamed to feature B. It's unfortunate, but the existing feature A // should be deprecated and re-created as a new feature with the same privileges. case1FeatureRename(deps); @@ -46,6 +49,8 @@ export const plugin: PluginInitializer = async (): Promise< // * `case_4_feature_b_v2` (new, decoupled from `ab` SO, partially replaces `case_4_feature_b`) // * `case_4_feature_c` (new, only for `ab` SO access) case4FeatureExtract(deps); + + initRoutes(core); }, start: () => {}, stop: () => {}, @@ -61,6 +66,7 @@ function case1FeatureRename(deps: PluginSetupDependencies) { all: { savedObject: { all: ['one'], read: [] }, ui: ['ui_all'] }, read: { savedObject: { all: [], read: ['one'] }, ui: ['ui_read'] }, }, + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], }; // Step 2: mark feature A as deprecated and provide proper replacements for all feature and @@ -96,6 +102,8 @@ function case2FeatureSplit(deps: PluginSetupDependencies) { deps.features.registerKibanaFeature({ deprecated: { notice: 'Case #2 is deprecated.' }, + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + app: ['app_one', 'app_two'], catalogue: ['cat_one', 'cat_two'], management: { kibana: ['management_one', 'management_two'] }, @@ -139,6 +147,8 @@ function case2FeatureSplit(deps: PluginSetupDependencies) { read: { savedObject: { all: [], read: ['one', 'two'] }, ui: ['ui_read_one', 'ui_read_two'], + catalogue: ['cat_one', 'cat_two'], + app: ['app_one', 'app_two'], replacedBy: [ { feature: 'case_2_feature_b', privileges: ['read'] }, { feature: 'case_2_feature_c', privileges: ['read'] }, @@ -149,6 +159,8 @@ function case2FeatureSplit(deps: PluginSetupDependencies) { // Step 2: define new features deps.features.registerKibanaFeature({ + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: 'case_2_feature_b', name: 'Case #2 feature B', @@ -182,10 +194,14 @@ function case2FeatureSplit(deps: PluginSetupDependencies) { read: { savedObject: { all: [], read: ['one'] }, ui: ['ui_read_one'], + catalogue: ['cat_one'], + app: ['app_one'], }, }, }); deps.features.registerKibanaFeature({ + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: 'case_2_feature_c', name: 'Case #2 feature C', @@ -219,6 +235,8 @@ function case2FeatureSplit(deps: PluginSetupDependencies) { read: { savedObject: { all: [], read: ['two'] }, ui: ['ui_read_two'], + app: ['app_two'], + catalogue: ['cat_two'], }, }, }); @@ -249,6 +267,8 @@ function case3FeatureSplitSubFeature(deps: PluginSetupDependencies) { deps.features.registerKibanaFeature({ deprecated: { notice: 'Case #3 is deprecated.' }, + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: 'case_3_feature_a', name: 'Case #3 feature A (DEPRECATED)', @@ -275,6 +295,8 @@ function case3FeatureSplitSubFeature(deps: PluginSetupDependencies) { // Step 2: Create a new feature with the desired privileges structure. deps.features.registerKibanaFeature({ + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: 'case_3_feature_a_v2', name: 'Case #3 feature A', @@ -324,6 +346,8 @@ function case4FeatureExtract(deps: PluginSetupDependencies) { deps.features.registerKibanaFeature({ deprecated: { notice: 'Case #4 is deprecated.' }, + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: `case_4_feature_${suffix.toLowerCase()}`, name: `Case #4 feature ${suffix} (DEPRECATED)`, @@ -350,6 +374,8 @@ function case4FeatureExtract(deps: PluginSetupDependencies) { // Step 2: introduce new features (v2) with privileges that don't grant access to `ab`. deps.features.registerKibanaFeature({ + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: `case_4_feature_${suffix.toLowerCase()}_v2`, name: `Case #4 feature ${suffix}`, @@ -363,6 +389,8 @@ function case4FeatureExtract(deps: PluginSetupDependencies) { // Step 3: introduce new feature C that only grants access to `ab`. deps.features.registerKibanaFeature({ + scope: [KibanaFeatureScope.Security, KibanaFeatureScope.Spaces], + category: DEFAULT_APP_CATEGORIES.kibana, id: 'case_4_feature_c', name: 'Case #4 feature C', diff --git a/x-pack/test/security_api_integration/plugins/features_provider/server/init_routes.ts b/x-pack/test/security_api_integration/plugins/features_provider/server/init_routes.ts new file mode 100644 index 0000000000000..d58f2f3078a3a --- /dev/null +++ b/x-pack/test/security_api_integration/plugins/features_provider/server/init_routes.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup } from '@kbn/core/server'; + +import type { PluginStartDependencies } from '.'; + +export function initRoutes(core: CoreSetup) { + const router = core.http.createRouter(); + + // This route mirrors existing `GET /api/features` route except that it also returns all deprecated features. + router.get( + { path: '/internal/features_provider/features', validate: false }, + async (context, request, response) => { + const [, pluginDeps] = await core.getStartServices(); + return response.ok({ + body: pluginDeps.features.getKibanaFeatures().map((feature) => feature.toRaw()), + }); + } + ); +} diff --git a/x-pack/test/security_api_integration/tests/features/deprecated_features.ts b/x-pack/test/security_api_integration/tests/features/deprecated_features.ts index 030f5ac704d8b..6e868fc5946ec 100644 --- a/x-pack/test/security_api_integration/tests/features/deprecated_features.ts +++ b/x-pack/test/security_api_integration/tests/features/deprecated_features.ts @@ -14,6 +14,7 @@ import type { FeatureKibanaPrivilegesReference, KibanaFeatureConfig, } from '@kbn/features-plugin/common'; +import { KibanaFeatureScope } from '@kbn/features-plugin/common'; import type { Role } from '@kbn/security-plugin-types-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; @@ -163,11 +164,97 @@ export default function ({ getService }: FtrProviderContext) { } }); + it('all deprecated features are known', async () => { + const { body: features } = await supertest + .get('/internal/features_provider/features') + .expect(200); + + // **NOTE**: This test is to ensure the AppEx Security team has a chance to review all features marked as + // deprecated. If you’re adding a new deprecated feature, make sure to add it to the list below manually or by + // running the API integration test locally with the --updateSnapshot flag. + expectSnapshot( + (features as KibanaFeatureConfig[]).flatMap((f) => (f.deprecated ? [f.id] : [])).sort() + ).toMatchInline(` + Array [ + "case_1_feature_a", + "case_2_feature_a", + "case_3_feature_a", + "case_4_feature_a", + "case_4_feature_b", + ] + `); + }); + + it('all deprecated features are replaced by a single feature only', async () => { + const featuresResponse = await supertest + .get('/internal/features_provider/features') + .expect(200); + const features = featuresResponse.body as KibanaFeatureConfig[]; + + // **NOTE**: This test ensures that deprecated features displayed in the Space’s feature visibility toggles screen + // are only replaced by a single feature. This way, if a feature is toggled off for a particular Space, there + // won’t be any ambiguity about which replacement feature should also be toggled off. Currently, we don’t + // anticipate having a deprecated feature replaced by more than one feature, so this test is intended to catch + // such scenarios early. If there’s a need for a deprecated feature to be replaced by multiple features, please + // reach out to the AppEx Security team to discuss how this should affect Space’s feature visibility toggles. + const featureIdsThatSupportMultipleReplacements = new Set([ + 'case_2_feature_a', + 'case_4_feature_a', + 'case_4_feature_b', + ]); + for (const feature of features) { + if ( + !feature.deprecated || + !feature.scope?.includes(KibanaFeatureScope.Spaces) || + featureIdsThatSupportMultipleReplacements.has(feature.id) + ) { + continue; + } + + // Collect all feature privileges including the ones provided by sub-features, if any. + const allPrivileges = Object.values(feature.privileges ?? {}).concat( + feature.subFeatures?.flatMap((subFeature) => + subFeature.privilegeGroups.flatMap(({ privileges }) => privileges) + ) ?? [] + ); + + // Collect all features IDs that are referenced by the deprecated feature privileges. + const referencedFeaturesIds = new Set(); + for (const privilege of allPrivileges) { + const replacedBy = privilege.replacedBy + ? 'default' in privilege.replacedBy + ? privilege.replacedBy.default.concat(privilege.replacedBy.minimal) + : privilege.replacedBy + : []; + for (const privilegeReference of replacedBy) { + referencedFeaturesIds.add(privilegeReference.feature); + } + } + + if (referencedFeaturesIds.size > 1) { + throw new Error( + `Feature "${feature.id}" is deprecated and replaced by more than one feature: ${ + referencedFeaturesIds.size + } features: ${Array.from(referencedFeaturesIds).join( + ', ' + )}. If it's intentional, please contact the AppEx Security team.` + ); + } + } + }); + it('all privileges of the deprecated features should have a proper replacement', async () => { // Fetch all features first. - const featuresResponse = await supertest.get('/api/features').expect(200); + const featuresResponse = await supertest + .get('/internal/features_provider/features') + .expect(200); const features = featuresResponse.body as KibanaFeatureConfig[]; + // Check if the action provided by the deprecated feature is directly replaceable by other + // features. The `ui:`-prefixed actions are special since they are prefixed with a feature ID, + // and do not need to be replaced like any other privilege actions. + const isReplaceableAction = (action: string) => !action.startsWith('ui:'); + // Collect all deprecated features. const deprecatedFeatures = features.filter((f) => f.deprecated); log.info(`Found ${deprecatedFeatures.length} deprecated features.`); @@ -207,7 +294,10 @@ export default function ({ getService }: FtrProviderContext) { ); for (const deprecatedAction of deprecatedActions) { - if (!replacementActions.has(deprecatedAction)) { + if ( + isReplaceableAction(deprecatedAction) && + !replacementActions.has(deprecatedAction) + ) { throw new Error( `Action "${deprecatedAction}" granted by the privilege "${privilegeId}" of the deprecated feature "${feature.id}" is not properly replaced.` ); @@ -225,22 +315,23 @@ export default function ({ getService }: FtrProviderContext) { .send({ applications: [] }) .expect(200); - // Both deprecated and new UI capabilities should be toggled. + // Only new UI capabilities should be toggled, deprecated ones should not be present. expect(capabilities).toEqual( expect.objectContaining({ - // UI flags from the deprecated feature privilege. - case_2_feature_a: { - ui_all_one: true, - ui_all_two: true, - ui_read_one: false, - ui_read_two: false, - }, - // UI flags from the feature privileges that replace deprecated one. case_2_feature_b: { ui_all_one: true, ui_read_one: false }, case_2_feature_c: { ui_all_two: true, ui_read_two: false }, }) ); + for (const deprecatedFeatureId of [ + 'case_1_feature_a', + 'case_2_feature_a', + 'case_3_feature_a', + 'case_4_feature_a', + 'case_4_feature_b', + ]) { + expect(capabilities).not.toHaveProperty(deprecatedFeatureId); + } }); it('Cases privileges are properly handled for deprecated privileges', async () => { diff --git a/x-pack/test/ui_capabilities/common/config.ts b/x-pack/test/ui_capabilities/common/config.ts index ba40e613c0d69..18a96b8e26274 100644 --- a/x-pack/test/ui_capabilities/common/config.ts +++ b/x-pack/test/ui_capabilities/common/config.ts @@ -46,6 +46,10 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) .filter((k) => k !== 'security') .map((key) => `--xpack.${key}.enabled=false`), `--plugin-path=${path.resolve(__dirname, 'plugins/foo_plugin')}`, + `--plugin-path=${path.resolve( + __dirname, + '../../security_api_integration/plugins/features_provider' + )}`, ], }, }; From 56b0ac2eda55b0fe1a3dc0cd445d704320b69981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georgiana-Andreea=20Onolea=C8=9B=C4=83?= Date: Wed, 6 Nov 2024 16:43:48 +0200 Subject: [PATCH 17/53] [ResponseOps][Connectors]Preconfigured connectors of disabled types show as disabled, but are actually enabled (#198792) Closes https://github.com/elastic/kibana/issues/190420 ## Summary - the preconfigured connectors should be displayed as enabled and not have the tooltip icon even if the xpack.actions.enabledActionTypes: [] setting is present in the kibana.yml (to disable all the connector types) ![Screenshot 2024-11-04 at 14 38 10](https://github.com/user-attachments/assets/ee817087-a079-481b-bf82-b7247f3ea923) --------- Co-authored-by: Antonio --- .../utils/check_action_type_enabled.test.ts | 20 +++++++++++++++++++ .../utils/check_action_type_enabled.ts | 5 +++-- .../actions_connectors_list.test.tsx | 15 +++++++++++++- .../components/actions_connectors_list.tsx | 7 ++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.test.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.test.ts index 987d95ef3d070..7794f83825c76 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.test.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.test.ts @@ -99,6 +99,26 @@ describe('checkActionTypeEnabled', () => { } `); }); + test('checkActionTypeEnabled returns true when actionType is disabled by config', async () => { + const actionType: ActionType = { + id: '1', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + name: 'my action', + enabled: false, + enabledInConfig: false, + enabledInLicense: true, + isSystemActionType: false, + }; + + const isPreconfiguredConnector = true; + + expect(checkActionTypeEnabled(actionType, isPreconfiguredConnector)).toMatchInlineSnapshot(` + Object { + "isEnabled": true, + } + `); + }); }); describe('checkActionFormActionTypeEnabled', () => { diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.ts index 891012f0eeb23..79c26b7052e86 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/check_action_type_enabled.ts @@ -22,13 +22,14 @@ export interface IsDisabledResult { } export const checkActionTypeEnabled = ( - actionType?: ActionType + actionType?: ActionType, + isPreconfiguredConnector: boolean = false ): IsEnabledResult | IsDisabledResult => { if (actionType?.enabledInLicense === false) { return getLicenseCheckResult(actionType); } - if (actionType?.enabledInConfig === false) { + if (actionType?.enabledInConfig === false && isPreconfiguredConnector === false) { return configurationCheckResult; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index a938aa6b7e6e5..1c191bf213852 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -750,6 +750,12 @@ describe('actions_connectors_list', () => { referencedByCount: 1, config: {}, }, + { + id: '3', + actionTypeId: 'test3', + isPreconfigured: true, + isDeprecated: false, + }, ] as ActionConnector[] } setActions={() => {}} @@ -766,7 +772,7 @@ describe('actions_connectors_list', () => { it('renders table of connectors', async () => { await setup(); expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1); - expect(wrapper.find('EuiTableRow')).toHaveLength(2); + expect(wrapper.find('EuiTableRow')).toHaveLength(3); expect(wrapper.find('EuiTableRow').at(0).prop('className')).toEqual( 'actConnectorsList__tableRowDisabled' ); @@ -774,6 +780,13 @@ describe('actions_connectors_list', () => { 'actConnectorsList__tableRowDisabled' ); }); + + it('renders preconfigured connectors as enabled', async () => { + await setup(); + expect(wrapper.find('EuiTableRow').at(2).prop('className')).not.toEqual( + 'actConnectorsList__tableRowDisabled' + ); + }); }); describe('component with deprecated connectors', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index e00b08d9c8512..17e6531a65c20 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -199,9 +199,9 @@ const ActionsConnectorsList = ({ truncateText: true, render: (value: string, item: ActionConnectorTableItem) => { const checkEnabledResult = checkActionTypeEnabled( - actionTypesIndex && actionTypesIndex[item.actionTypeId] + actionTypesIndex && actionTypesIndex[item.actionTypeId], + item.isPreconfigured ); - /** * TODO: Remove when connectors can provide their own UX message. * Issue: https://github.com/elastic/kibana/issues/114507 @@ -363,7 +363,8 @@ const ActionsConnectorsList = ({ columns={actionsTableColumns} rowProps={(item: ActionConnectorTableItem) => ({ className: - !actionTypesIndex || !actionTypesIndex[item.actionTypeId]?.enabled + !item.isPreconfigured && + (!actionTypesIndex || !actionTypesIndex[item.actionTypeId]?.enabled) ? 'actConnectorsList__tableRowDisabled' : '', 'data-test-subj': 'connectors-row', From 730f4c9d2a5119a9f30e9b94ccb28ec418af270a Mon Sep 17 00:00:00 2001 From: Gerard Soldevila Date: Wed, 6 Nov 2024 15:44:35 +0100 Subject: [PATCH 18/53] Address some of the `no_group_crossing` dependencies (#198261) ### Summary This PR relocates some plugins and packages that are incorrectly categorised, aiming at reducing inter-solution dependencies. It also fixes some incorrect import statements that introduce unnecessary dependencies with other components. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Maryam Saeidi --- .eslintrc.js | 50 ++++++------------- .../page/analytics_no_data/impl/kibana.jsonc | 4 +- .../page/analytics_no_data/mocks/kibana.jsonc | 4 +- .../page/analytics_no_data/types/kibana.jsonc | 4 +- .../selection/kibana.jsonc | 5 +- .../exploratory_view_example/kibana.jsonc | 2 + .../screenshotting_example/kibana.jsonc | 4 ++ .../ui_actions_enhanced_examples/kibana.jsonc | 4 ++ x-pack/packages/kbn-ai-assistant/kibana.jsonc | 4 +- .../security-solution/data_table/kibana.jsonc | 4 +- .../plugins/dashboard_enhanced/kibana.jsonc | 4 +- x-pack/plugins/data_quality/kibana.jsonc | 2 + x-pack/plugins/ingest_pipelines/kibana.jsonc | 2 + .../telemetry_collection/get_licenses.ts | 6 +-- x-pack/plugins/monitoring/tsconfig.json | 1 - .../observability_solution/infra/kibana.jsonc | 2 + .../inventory/kibana.jsonc | 2 + .../investigate_app/kibana.jsonc | 2 + .../lib/adapters/framework/adapter_types.ts | 2 - .../metrics_data_access/tsconfig.json | 1 - .../observability/common/typings.ts | 7 ++- .../alerts_table/common/get_columns.tsx | 31 ++---------- .../common/render_cell_value.test.tsx | 22 -------- .../alerts_table/common/render_cell_value.tsx | 19 +++---- .../alerts_table/slo/default_columns.tsx | 13 ++--- .../components/custom_threshold/types.ts | 48 +++++++++--------- .../pages/alerts/components/alert_actions.tsx | 6 +-- .../public/utils/test_helper.tsx | 3 +- .../observability/tsconfig.json | 2 - .../observability_ai_assistant/kibana.jsonc | 6 +-- .../kibana.jsonc | 2 + .../public/helpers/test_helper.tsx | 3 +- .../tsconfig.json | 1 - .../slo/public/utils/test_helper.tsx | 3 +- .../observability_solution/slo/tsconfig.json | 1 - .../synthetics/e2e/kibana.jsonc | 2 + x-pack/plugins/screenshotting/kibana.jsonc | 4 +- x-pack/plugins/search_assistant/kibana.jsonc | 2 + .../server/index.ts | 2 - .../es_deprecation_logs.test.tsx | 3 -- .../helpers/app_context.mock.ts | 1 - x-pack/plugins/upgrade_assistant/kibana.jsonc | 3 +- .../upgrade_assistant/public/plugin.ts | 6 +-- .../plugins/upgrade_assistant/public/types.ts | 1 - 44 files changed, 124 insertions(+), 176 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f751c9692c996..730c9599f23f9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1992,43 +1992,23 @@ module.exports = { }, { files: [ - 'packages/kbn-reporting/common/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/kbn-reporting/export_types/pdf_common/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/kbn-reporting/export_types/pdf/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/kbn-reporting/export_types/png_common/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/kbn-reporting/export_types/png/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/kbn-reporting/public/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/kbn-reporting/server/**', // TODO @elastic/appex-sharedux - A package depending on a plugin: @kbn/screenshotting-plugin, can we move theser interfaces to a platform/shared package? - 'packages/shared-ux/page/analytics_no_data/types/**', - 'scripts/create_observability_rules.js', // TODO - is importing "@kbn/observability-alerting-test-data" (observability/private) - 'src/cli_setup/**', // TODO @kibana/operations - is importing "@kbn/interactive-setup-plugin" (platform/private) - 'src/dev/build/tasks/install_chromium.ts', // TODO @kibana/operations - is importing "@kbn/screenshotting-plugin" (platform/private) - 'src/plugins/ai_assistant_management/selection/**', - 'src/plugins/dashboard/**', - 'src/plugins/discover/**', - 'test/**', - 'x-pack/examples/exploratory_view_example/**', - 'x-pack/examples/screenshotting_example/**', - 'x-pack/examples/ui_actions_enhanced_examples/**', - 'x-pack/packages/security-solution/data_table/**', - 'x-pack/plugins/aiops/**', - 'x-pack/plugins/data_quality/**', - 'x-pack/plugins/ingest_pipelines/**', - 'x-pack/plugins/ml/**', - 'x-pack/plugins/monitoring/**', - 'x-pack/plugins/observability_solution/infra/**', - 'x-pack/plugins/observability_solution/inventory/**', - 'x-pack/plugins/observability_solution/investigate_app/**', - 'x-pack/plugins/observability_solution/investigate/**', + // logsShared depends on o11y/private plugins, but platform plugins depend on it 'x-pack/plugins/observability_solution/logs_shared/**', - 'x-pack/plugins/observability_solution/metrics_data_access/**', - 'x-pack/plugins/observability_solution/observability_ai_assistant_app/**', - 'x-pack/plugins/observability_solution/observability_ai_assistant_management/**', - 'x-pack/plugins/observability_solution/observability/**', - 'x-pack/plugins/observability_solution/slo/**', - 'x-pack/plugins/observability_solution/synthetics/e2e/**', + + // this plugin depends on visTypeTimeseries plugin (for TSVB viz) which is platform/private ATM + 'x-pack/plugins/observability_solution/infra/**', + + // TODO @kibana/operations + 'scripts/create_observability_rules.js', // is importing "@kbn/observability-alerting-test-data" (observability/private) + 'src/cli_setup/**', // is importing "@kbn/interactive-setup-plugin" (platform/private) + 'src/dev/build/tasks/install_chromium.ts', // is importing "@kbn/screenshotting-plugin" (platform/private) + + // @kbn/osquery-plugin could be categorised as Security, but @kbn/infra-plugin (observability) depends on it! 'x-pack/plugins/osquery/**', - 'x-pack/plugins/search_assistant/**', + + // For now, we keep the exception to let tests depend on anythying. + // Ideally, we need to classify the solution specific ones to reduce CI times + 'test/**', 'x-pack/test_serverless/**', 'x-pack/test/**', 'x-pack/test/plugin_functional/plugins/resolver_test/**', diff --git a/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc b/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc index b8690de58bdb9..45c7a028be286 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc +++ b/packages/shared-ux/page/analytics_no_data/impl/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-analytics-no-data", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc b/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc index cde1666e15f14..e7d570e4239e6 100644 --- a/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc +++ b/packages/shared-ux/page/analytics_no_data/mocks/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/shared-ux-page-analytics-no-data-mocks", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc b/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc index df5498181fe69..fd1740c0d757e 100644 --- a/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc +++ b/packages/shared-ux/page/analytics_no_data/types/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-browser", "id": "@kbn/shared-ux-page-analytics-no-data-types", - "owner": "@elastic/appex-sharedux" + "owner": "@elastic/appex-sharedux", + "group": "platform", + "visibility": "private" } diff --git a/src/plugins/ai_assistant_management/selection/kibana.jsonc b/src/plugins/ai_assistant_management/selection/kibana.jsonc index 715b90f1d4d79..74640423685a9 100644 --- a/src/plugins/ai_assistant_management/selection/kibana.jsonc +++ b/src/plugins/ai_assistant_management/selection/kibana.jsonc @@ -4,8 +4,9 @@ "owner": [ "@elastic/obs-knowledge-team" ], + // This should probably be platform. While the code owner is currently observability, the package is a platform AI assistant selector. "group": "platform", - "visibility": "private", + "visibility": "shared", "plugin": { "id": "aiAssistantManagementSelection", "browser": true, @@ -25,4 +26,4 @@ "kibanaReact" ] } -} \ No newline at end of file +} diff --git a/x-pack/examples/exploratory_view_example/kibana.jsonc b/x-pack/examples/exploratory_view_example/kibana.jsonc index 6cf8fa64983ac..cf077336b0f90 100644 --- a/x-pack/examples/exploratory_view_example/kibana.jsonc +++ b/x-pack/examples/exploratory_view_example/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/exploratory-view-example-plugin", "owner": "@elastic/obs-ux-infra_services-team", + "group": "observability", + "visibility": "private", "plugin": { "id": "exploratoryViewExample", "server": false, diff --git a/x-pack/examples/screenshotting_example/kibana.jsonc b/x-pack/examples/screenshotting_example/kibana.jsonc index 127706ad42e3d..3519bc91caa66 100644 --- a/x-pack/examples/screenshotting_example/kibana.jsonc +++ b/x-pack/examples/screenshotting_example/kibana.jsonc @@ -2,6 +2,10 @@ "type": "plugin", "id": "@kbn/screenshotting-example-plugin", "owner": "@elastic/appex-sharedux", + // This plugin is not meant to be referenced or imported + "visibility": "private", + // If cloned / used as an inspiration, please bear in mind that your plugin might belong to a specific solution group + "group": "platform", "description": "An example integration with the screenshotting plugin.", "plugin": { "id": "screenshottingExample", diff --git a/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc b/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc index 1da3e4f182875..25211ae2063bd 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc +++ b/x-pack/examples/ui_actions_enhanced_examples/kibana.jsonc @@ -2,6 +2,10 @@ "type": "plugin", "id": "@kbn/ui-actions-enhanced-examples-plugin", "owner": "@elastic/appex-sharedux", + // This plugin is not meant to be referenced or imported + "visibility": "private", + // If cloned / used as an inspiration, please bear in mind that your plugin might belong to a specific solution group + "group": "platform", "plugin": { "id": "uiActionsEnhancedExamples", "server": false, diff --git a/x-pack/packages/kbn-ai-assistant/kibana.jsonc b/x-pack/packages/kbn-ai-assistant/kibana.jsonc index f8da31c9d6749..625dedc6c99f4 100644 --- a/x-pack/packages/kbn-ai-assistant/kibana.jsonc +++ b/x-pack/packages/kbn-ai-assistant/kibana.jsonc @@ -2,6 +2,6 @@ "id": "@kbn/ai-assistant", "owner": "@elastic/search-kibana", "type": "shared-browser", - "group": "observability", - "visibility": "private" + "group": "platform", + "visibility": "shared" } diff --git a/x-pack/packages/security-solution/data_table/kibana.jsonc b/x-pack/packages/security-solution/data_table/kibana.jsonc index 5298db752359f..9695411a65301 100644 --- a/x-pack/packages/security-solution/data_table/kibana.jsonc +++ b/x-pack/packages/security-solution/data_table/kibana.jsonc @@ -1,5 +1,7 @@ { "type": "shared-common", "id": "@kbn/securitysolution-data-table", - "owner": "@elastic/security-threat-hunting-investigations" + "owner": "@elastic/security-threat-hunting-investigations", + "group": "security", + "visibility": "private" } diff --git a/x-pack/plugins/dashboard_enhanced/kibana.jsonc b/x-pack/plugins/dashboard_enhanced/kibana.jsonc index 36f8a02e65ce9..ae6902cc3c714 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.jsonc +++ b/x-pack/plugins/dashboard_enhanced/kibana.jsonc @@ -5,7 +5,7 @@ "@elastic/kibana-presentation" ], "group": "platform", - "visibility": "private", + "visibility": "shared", "plugin": { "id": "dashboardEnhanced", "browser": true, @@ -31,4 +31,4 @@ "uiActions" ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/data_quality/kibana.jsonc b/x-pack/plugins/data_quality/kibana.jsonc index ad1a64d4ed140..dc54e20f40bd7 100644 --- a/x-pack/plugins/data_quality/kibana.jsonc +++ b/x-pack/plugins/data_quality/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/data-quality-plugin", "owner": "@elastic/obs-ux-logs-team", + "group": "observability", + "visibility": "private", "plugin": { "id": "dataQuality", "server": true, diff --git a/x-pack/plugins/ingest_pipelines/kibana.jsonc b/x-pack/plugins/ingest_pipelines/kibana.jsonc index 55fa46c61b377..85b3e43aedf4d 100644 --- a/x-pack/plugins/ingest_pipelines/kibana.jsonc +++ b/x-pack/plugins/ingest_pipelines/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/ingest-pipelines-plugin", "owner": "@elastic/kibana-management", + "group": "platform", + "visibility": "shared", "plugin": { "id": "ingestPipelines", "server": true, diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts index 4ea0077c21ba8..98f3932984546 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ESLicense } from '@kbn/telemetry-collection-xpack-plugin/server'; +import type { LicenseGetLicenseInformation } from '@elastic/elasticsearch/lib/api/types'; import { INDEX_PATTERN_ELASTICSEARCH, USAGE_FETCH_INTERVAL } from '../../common/constants'; /** @@ -18,7 +18,7 @@ export async function getLicenses( callCluster: ElasticsearchClient, timestamp: number, maxBucketSize: number -): Promise<{ [clusterUuid: string]: ESLicense | undefined }> { +): Promise<{ [clusterUuid: string]: LicenseGetLicenseInformation | undefined }> { const response = await fetchLicenses(callCluster, clusterUuids, timestamp, maxBucketSize); return handleLicenses(response); } @@ -76,7 +76,7 @@ export async function fetchLicenses( export interface ESClusterStatsWithLicense { cluster_uuid: string; type: 'cluster_stats'; - license?: ESLicense; + license?: LicenseGetLicenseInformation; } /** diff --git a/x-pack/plugins/monitoring/tsconfig.json b/x-pack/plugins/monitoring/tsconfig.json index 957b256bd726b..3b78104e65b8c 100644 --- a/x-pack/plugins/monitoring/tsconfig.json +++ b/x-pack/plugins/monitoring/tsconfig.json @@ -19,7 +19,6 @@ "@kbn/features-plugin", "@kbn/infra-plugin", "@kbn/licensing-plugin", - "@kbn/telemetry-collection-xpack-plugin", "@kbn/triggers-actions-ui-plugin", "@kbn/expect", "@kbn/i18n", diff --git a/x-pack/plugins/observability_solution/infra/kibana.jsonc b/x-pack/plugins/observability_solution/infra/kibana.jsonc index 0f039fb02e356..6bcc6ef7259b4 100644 --- a/x-pack/plugins/observability_solution/infra/kibana.jsonc +++ b/x-pack/plugins/observability_solution/infra/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/infra-plugin", "owner": ["@elastic/obs-ux-logs-team", "@elastic/obs-ux-infra_services-team"], + "group": "observability", + "visibility": "private", "description": "This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions", "plugin": { "id": "infra", diff --git a/x-pack/plugins/observability_solution/inventory/kibana.jsonc b/x-pack/plugins/observability_solution/inventory/kibana.jsonc index fc77163ae3c5f..e7cc398c9c655 100644 --- a/x-pack/plugins/observability_solution/inventory/kibana.jsonc +++ b/x-pack/plugins/observability_solution/inventory/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/inventory-plugin", "owner": "@elastic/obs-ux-infra_services-team", + "group": "observability", + "visibility": "private", "plugin": { "id": "inventory", "server": true, diff --git a/x-pack/plugins/observability_solution/investigate_app/kibana.jsonc b/x-pack/plugins/observability_solution/investigate_app/kibana.jsonc index e55dc03c83266..9b8284808a657 100644 --- a/x-pack/plugins/observability_solution/investigate_app/kibana.jsonc +++ b/x-pack/plugins/observability_solution/investigate_app/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/investigate-app-plugin", "owner": "@elastic/obs-ux-management-team", + "group": "observability", + "visibility": "private", "plugin": { "id": "investigateApp", "server": true, diff --git a/x-pack/plugins/observability_solution/metrics_data_access/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/observability_solution/metrics_data_access/server/lib/adapters/framework/adapter_types.ts index daea1177b19f8..246988ed96307 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/server/lib/adapters/framework/adapter_types.ts @@ -17,7 +17,6 @@ import { } from '@kbn/data-plugin/server'; import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import { HomeServerPluginSetup } from '@kbn/home-plugin/server'; -import { VisTypeTimeseriesSetup } from '@kbn/vis-type-timeseries-plugin/server'; import { FeaturesPluginSetup } from '@kbn/features-plugin/server'; import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { PluginSetupContract as AlertingPluginContract } from '@kbn/alerting-plugin/server'; @@ -37,7 +36,6 @@ export interface InfraServerPluginSetupDeps { share: SharePluginSetup; spaces: SpacesPluginSetup; usageCollection: UsageCollectionSetup; - visTypeTimeseries: VisTypeTimeseriesSetup; ml?: MlPluginSetup; metricsDataAccess: MetricsDataPluginSetup; } diff --git a/x-pack/plugins/observability_solution/metrics_data_access/tsconfig.json b/x-pack/plugins/observability_solution/metrics_data_access/tsconfig.json index 0c2c471a6bf77..2889bddc82393 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/metrics_data_access/tsconfig.json @@ -16,7 +16,6 @@ "@kbn/data-plugin", "@kbn/data-views-plugin", "@kbn/home-plugin", - "@kbn/vis-type-timeseries-plugin", "@kbn/features-plugin", "@kbn/spaces-plugin", "@kbn/alerting-plugin", diff --git a/x-pack/plugins/observability_solution/observability/common/typings.ts b/x-pack/plugins/observability_solution/observability/common/typings.ts index 03981f5941dc2..6c4eb09b284b7 100644 --- a/x-pack/plugins/observability_solution/observability/common/typings.ts +++ b/x-pack/plugins/observability_solution/observability/common/typings.ts @@ -11,7 +11,7 @@ import { ALERT_STATUS_RECOVERED, ALERT_STATUS_UNTRACKED, } from '@kbn/rule-data-utils'; -import { Filter } from '@kbn/es-query'; +import type { Filter } from '@kbn/es-query'; import { ALERT_STATUS_ALL } from './constants'; export type Maybe = T | null | undefined; @@ -54,5 +54,10 @@ export interface TimeRange { to?: string; } +export interface EventNonEcsData { + field: string; + value?: Maybe; +} + // Alert fields['kibana.alert.group] type export type GroupBy = Group[]; diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/get_columns.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/get_columns.tsx index 2d2c1b1c299cf..fa2f10c3516e7 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/get_columns.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/get_columns.tsx @@ -5,11 +5,7 @@ * 2.0. */ -/** - * We need to produce types and code transpilation at different folders during the build of the package. - * We have types and code at different imports because we don't want to import the whole package in the resulting webpack bundle for the plugin. - * This way plugins can do targeted imports to reduce the final code bundle - */ +import type { EuiDataGridColumn } from '@elastic/eui'; import { ALERT_EVALUATION_VALUE, ALERT_EVALUATION_THRESHOLD, @@ -21,30 +17,18 @@ import { ALERT_INSTANCE_ID, TAGS, } from '@kbn/rule-data-utils'; -import { EuiDataGridColumn } from '@elastic/eui'; -import type { ColumnHeaderOptions } from '@kbn/timelines-plugin/common'; import { i18n } from '@kbn/i18n'; -/** - * columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface, - * plus additional TGrid column properties - */ export const getColumns = ( { showRuleName, }: { showRuleName?: boolean; } = { showRuleName: false } -): Array< - Pick & ColumnHeaderOptions -> => { - const ruleNameColumn: Array< - Pick & - ColumnHeaderOptions - > = showRuleName +): EuiDataGridColumn[] => { + const ruleNameColumn: EuiDataGridColumn[] = showRuleName ? [ { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate( 'xpack.observability.alertsTGrid.ruleNameColumnDescription', { @@ -59,7 +43,6 @@ export const getColumns = ( return [ { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.statusColumnDescription', { defaultMessage: 'Alert Status', }), @@ -67,7 +50,6 @@ export const getColumns = ( initialWidth: 120, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.triggeredColumnDescription', { defaultMessage: 'Triggered', }), @@ -76,7 +58,6 @@ export const getColumns = ( schema: 'datetime', }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.durationColumnDescription', { defaultMessage: 'Duration', }), @@ -85,7 +66,6 @@ export const getColumns = ( }, ...ruleNameColumn, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.sourceColumnDescription', { defaultMessage: 'Group', }), @@ -93,7 +73,6 @@ export const getColumns = ( initialWidth: 100, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate( 'xpack.observability.alertsTGrid.observedValueColumnDescription', { @@ -104,7 +83,6 @@ export const getColumns = ( initialWidth: 100, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.thresholdColumnDescription', { defaultMessage: 'Threshold', }), @@ -112,7 +90,6 @@ export const getColumns = ( initialWidth: 100, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.tagsColumnDescription', { defaultMessage: 'Tags', }), @@ -120,12 +97,10 @@ export const getColumns = ( initialWidth: 150, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonColumnDescription', { defaultMessage: 'Reason', }), id: ALERT_REASON, - linkField: '*', }, ]; }; diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx index d551f90f1097f..d9d01d85c5303 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.test.tsx @@ -6,7 +6,6 @@ */ import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils'; -import type { DeprecatedCellValueElementProps } from '@kbn/timelines-plugin/common'; import { render } from '../../../utils/test_helper'; import { getRenderCellValue } from './render_cell_value'; @@ -19,7 +18,6 @@ describe('getRenderCellValue', () => { it('should return an active indicator when alert status is active', async () => { const cell = render( getRenderCellValue({ - ...requiredProperties, columnId: ALERT_STATUS, data: makeAlertsTableRow({ alertStatus: ALERT_STATUS_ACTIVE }), }) @@ -31,7 +29,6 @@ describe('getRenderCellValue', () => { it('should return a recovered indicator when alert status is recovered', async () => { const cell = render( getRenderCellValue({ - ...requiredProperties, columnId: ALERT_STATUS, data: makeAlertsTableRow({ alertStatus: ALERT_STATUS_RECOVERED }), }) @@ -50,22 +47,3 @@ function makeAlertsTableRow({ alertStatus }: AlertsTableRow) { }, ]; } - -const requiredProperties: DeprecatedCellValueElementProps = { - rowIndex: 0, - colIndex: 0, - columnId: '', - setCellProps: jest.fn(), - isExpandable: false, - isExpanded: false, - isDetails: false, - data: [], - eventId: '', - header: { - id: '', - columnHeaderType: 'not-filtered', - }, - isDraggable: false, - linkValues: [], - scopeId: '', -}; diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx index 6f6eb54a333d1..23757f889e058 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/common/render_cell_value.tsx @@ -23,7 +23,7 @@ import { ALERT_RULE_EXECUTION_TIMESTAMP, } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash'; -import type { TimelineNonEcsData } from '@kbn/timelines-plugin/common'; +import type { EventNonEcsData } from '../../../../common/typings'; import type { ObservabilityRuleTypeRegistry } from '../../..'; import { asDuration } from '../../../../common/utils/formatters'; import { AlertSeverityBadge } from '../../alert_severity_badge'; @@ -36,7 +36,7 @@ export const getMappedNonEcsValue = ({ data, fieldName, }: { - data: TimelineNonEcsData[]; + data: EventNonEcsData[]; fieldName: string; }): string[] | undefined => { const item = data.find((d) => d.field === fieldName); @@ -64,23 +64,24 @@ const getRenderValue = (mappedNonEcsValue: any) => { return '--'; }; +interface GetRenderCellValueParams { + columnId: string; + data?: EventNonEcsData[]; + setFlyoutAlert?: (alertId: string) => void; + observabilityRuleTypeRegistry?: ObservabilityRuleTypeRegistry; +} + /** * This implementation of `EuiDataGrid`'s `renderCellValue` * accepts `EuiDataGridCellValueElementProps`, plus `data` * from the TGrid */ - export const getRenderCellValue = ({ columnId, data, setFlyoutAlert, observabilityRuleTypeRegistry, -}: { - columnId: string; - data?: Array<{ field: string; value: any }>; - setFlyoutAlert?: (alertId: string) => void; - observabilityRuleTypeRegistry?: ObservabilityRuleTypeRegistry; -}) => { +}: GetRenderCellValueParams) => { if (!data) return null; const mappedNonEcsValue = getMappedNonEcsValue({ data, diff --git a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/default_columns.tsx b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/default_columns.tsx index ea67c7c158e83..3796667e2583f 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/default_columns.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alerts_table/slo/default_columns.tsx @@ -10,20 +10,17 @@ * We have types and code at different imports because we don't want to import the whole package in the resulting webpack bundle for the plugin. * This way plugins can do targeted imports to reduce the final code bundle */ + +import type { EuiDataGridColumn } from '@elastic/eui'; import { ALERT_DURATION, ALERT_REASON, ALERT_STATUS, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; -import { EuiDataGridColumn } from '@elastic/eui'; -import type { ColumnHeaderOptions } from '@kbn/timelines-plugin/common'; import { i18n } from '@kbn/i18n'; /** * columns implements a subset of `EuiDataGrid`'s `EuiDataGridColumn` interface, * plus additional TGrid column properties */ -export const columns: Array< - Pick & ColumnHeaderOptions -> = [ +export const columns: EuiDataGridColumn[] = [ { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate( 'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.statusColumnDescription', { @@ -34,7 +31,6 @@ export const columns: Array< initialWidth: 110, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate( 'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.durationColumnDescription', { @@ -45,7 +41,6 @@ export const columns: Array< initialWidth: 116, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate( 'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.sloColumnDescription', { @@ -56,7 +51,6 @@ export const columns: Array< initialWidth: 110, }, { - columnHeaderType: 'not-filtered', displayAsText: i18n.translate( 'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.reasonColumnDescription', { @@ -64,6 +58,5 @@ export const columns: Array< } ), id: ALERT_REASON, - linkField: '*', }, ]; diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts index 891661b6bc82a..2c04cdf8f0767 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/types.ts @@ -6,35 +6,36 @@ */ import * as rt from 'io-ts'; -import { CasesPublicStart } from '@kbn/cases-plugin/public'; -import { ChartsPluginStart } from '@kbn/charts-plugin/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DiscoverStart } from '@kbn/discover-plugin/public'; -import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; -import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import { LensPublicStart } from '@kbn/lens-plugin/public'; -import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; -import { OsqueryPluginStart } from '@kbn/osquery-plugin/public'; +import type { CasesPublicStart } from '@kbn/cases-plugin/public'; +import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DataView, DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; +import type { OsqueryPluginStart } from '@kbn/osquery-plugin/public'; import { ALERT_GROUP, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils'; -import { SharePluginStart } from '@kbn/share-plugin/public'; -import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import { +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import type { RuleTypeParams, TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; -import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { Group } from '../../../common/typings'; import { aggType, - CustomThresholdSearchSourceFields, - BaseMetricExpressionParams, - CustomMetricExpressionParams, - MetricExpressionParams, - ThresholdParams, + type CustomThresholdSearchSourceFields, + type BaseMetricExpressionParams, + type CustomMetricExpressionParams, + type MetricExpressionParams, + type ThresholdParams, } from '../../../common/custom_threshold_rule/types'; -import { ObservabilityPublicStart } from '../../plugin'; +import type { ObservabilityPublicStart } from '../../plugin'; export type CustomThresholdPrefillOptions = Partial< Omit & { criteria: Array> } @@ -90,8 +91,9 @@ export interface CustomThresholdRuleTypeParams extends RuleTypeParams { searchConfiguration: CustomThresholdSearchSourceFields; groupBy?: string | string[]; } + export interface CustomThresholdAlertFields { - [ALERT_GROUP]?: Array<{ field: string; value: string }>; + [ALERT_GROUP]?: Group[]; [ALERT_RULE_PARAMETERS]: CustomThresholdRuleTypeParams; } diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alerts/components/alert_actions.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alerts/components/alert_actions.tsx index f591347b17238..071b75ab89632 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alerts/components/alert_actions.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alerts/components/alert_actions.tsx @@ -19,15 +19,15 @@ import { i18n } from '@kbn/i18n'; import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; import { AttachmentType } from '@kbn/cases-plugin/common'; import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { TimelineNonEcsData } from '@kbn/timelines-plugin/common'; import type { AlertActionsProps } from '@kbn/triggers-actions-ui-plugin/public/types'; import { useRouteMatch } from 'react-router-dom'; import { SLO_ALERTS_TABLE_ID } from '@kbn/observability-shared-plugin/common'; +import type { EventNonEcsData } from '../../../../common/typings'; import { RULE_DETAILS_PAGE_ID } from '../../rule_details/constants'; import { paths, SLO_DETAIL_PATH } from '../../../../common/locators/paths'; import { useKibana } from '../../../utils/kibana_react'; import { parseAlert } from '../helpers/parse_alert'; -import { observabilityFeatureId, ObservabilityRuleTypeRegistry } from '../../..'; +import { observabilityFeatureId, type ObservabilityRuleTypeRegistry } from '../../..'; import type { ConfigSchema } from '../../../plugin'; import { ALERT_DETAILS_PAGE_ID } from '../../alert_details/alert_details'; @@ -58,7 +58,7 @@ export function AlertActions({ const data = useMemo( () => - Object.entries(alert ?? {}).reduce( + Object.entries(alert ?? {}).reduce( (acc, [field, value]) => [...acc, { field, value: value as string[] }], [] ), diff --git a/x-pack/plugins/observability_solution/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability_solution/observability/public/utils/test_helper.tsx index dca5d29851469..16184c5621594 100644 --- a/x-pack/plugins/observability_solution/observability/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability_solution/observability/public/utils/test_helper.tsx @@ -14,7 +14,6 @@ import { coreMock } from '@kbn/core/public/mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import translations from '@kbn/translations-plugin/translations/ja-JP.json'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -52,7 +51,7 @@ const queryClient = new QueryClient({ export const render = (component: React.ReactNode, config: Subset = {}) => { return testLibRender( - + + diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json index 99bce73e1722f..f0ad230f6f1b3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json @@ -19,7 +19,6 @@ "@kbn/core-chrome-browser", "@kbn/observability-ai-assistant-plugin", "@kbn/serverless", - "@kbn/translations-plugin", "@kbn/enterprise-search-plugin", "@kbn/management-settings-components-field-row", "@kbn/observability-shared-plugin", diff --git a/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx b/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx index 1fe6ece726610..014644d973b74 100644 --- a/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx +++ b/x-pack/plugins/observability_solution/slo/public/utils/test_helper.tsx @@ -13,7 +13,6 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { createObservabilityRuleTypeRegistryMock } from '@kbn/observability-plugin/public'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import translations from '@kbn/translations-plugin/translations/ja-JP.json'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render as testLibRender } from '@testing-library/react'; import React from 'react'; @@ -43,7 +42,7 @@ const queryClient = new QueryClient({ export const render = (component: React.ReactNode) => { return testLibRender( // @ts-ignore - + { prepend: (url: string) => url, }, }, - plugins: { - infra: {}, - }, }); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts index 0583c79eb3eff..31fd69648418f 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts @@ -108,7 +108,6 @@ export const getAppContextMock = (kibanaVersion: SemVer) => ({ }, plugins: { share: shareMock, - infra: undefined, cloud: { ...cloudMock.createSetup(), isCloudEnabled: false, diff --git a/x-pack/plugins/upgrade_assistant/kibana.jsonc b/x-pack/plugins/upgrade_assistant/kibana.jsonc index a339a06c9e60d..55a08297937bb 100644 --- a/x-pack/plugins/upgrade_assistant/kibana.jsonc +++ b/x-pack/plugins/upgrade_assistant/kibana.jsonc @@ -2,6 +2,8 @@ "type": "plugin", "id": "@kbn/upgrade-assistant-plugin", "owner": "@elastic/kibana-management", + "group": "platform", + "visibility": "private", "plugin": { "id": "upgradeAssistant", "server": true, @@ -21,7 +23,6 @@ "usageCollection", "cloud", "security", - "infra", "logsShared" ], "requiredBundles": [ diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index 694e29cd7cee3..ceeb674fef961 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -51,7 +51,7 @@ export class UpgradeAssistantUIPlugin title: pluginName, order: 1, async mount(params) { - const [coreStart, { data, ...plugins }] = await coreSetup.getStartServices(); + const [coreStart, { data }] = await coreSetup.getStartServices(); const { chrome: { docTitle }, @@ -65,10 +65,6 @@ export class UpgradeAssistantUIPlugin plugins: { cloud, share, - // Infra plugin doesnt export anything as a public interface. So the only - // way we have at this stage for checking if the plugin is available or not - // is by checking if the startServices has the `infra` key. - infra: Object.hasOwn(plugins, 'infra') ? {} : undefined, }, services: { core: coreStart, diff --git a/x-pack/plugins/upgrade_assistant/public/types.ts b/x-pack/plugins/upgrade_assistant/public/types.ts index f91a17323b02b..f01271443cb7f 100644 --- a/x-pack/plugins/upgrade_assistant/public/types.ts +++ b/x-pack/plugins/upgrade_assistant/public/types.ts @@ -47,7 +47,6 @@ export interface AppDependencies { plugins: { cloud?: CloudSetup; share: SharePluginSetup; - infra: object | undefined; }; services: { core: CoreStart; From 8ed8cc964e2ec557ea7173cc8cecf17b7f69d0d3 Mon Sep 17 00:00:00 2001 From: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:45:22 +0100 Subject: [PATCH 19/53] [Search] Move `ConnectorDefinition` types and consts (#197676) ## Summary - Move types and consts for `ConnectorClientSideDefinition` and `ConnectorServerSideDefinition` to the shared `kbn-search-connectors` package - Update ESS references to these values to use it from the package - Remove them from the connectors plugin --- packages/kbn-optimizer/limits.yml | 2 +- .../constants}/connectors.ts | 467 +++-- .../constants}/doc_links.ts | 8 +- .../kbn-search-connectors/constants/index.ts | 11 + packages/kbn-search-connectors/index.ts | 1 + packages/kbn-search-connectors/tsconfig.json | 1 + .../types/connector_definition.ts | 29 + packages/kbn-search-connectors/types/index.ts | 1 + .../types/native_connectors.ts | 1844 ++++++++--------- .../method_connector/new_connector_logic.ts | 3 +- .../new_index/new_search_index_page.tsx | 2 +- .../native_connector_configuration_config.tsx | 11 +- .../research_configuration.tsx | 3 +- .../shared/kibana/kibana_logic.ts | 2 +- .../server/utils/search_result_provider.ts | 2 +- .../search_connectors/common/constants.ts | 182 -- .../common/lib/connector_types.ts | 10 +- .../plugins/search_connectors/common/types.ts | 17 - .../plugins/search_connectors/public/index.ts | 1 - .../search_connectors/public/plugin.ts | 2 +- .../plugins/search_connectors/public/types.ts | 3 +- .../plugins/search_connectors/server/index.ts | 1 - .../search_connectors/server/plugin.ts | 2 +- .../plugins/search_connectors/server/types.ts | 2 +- .../plugins/search_connectors/tsconfig.json | 3 +- .../translations/translations/fr-FR.json | 133 +- .../translations/translations/ja-JP.json | 133 +- .../translations/translations/zh-CN.json | 133 +- 28 files changed, 1427 insertions(+), 1582 deletions(-) rename {x-pack/plugins/search_connectors/common => packages/kbn-search-connectors/constants}/connectors.ts (50%) rename {x-pack/plugins/search_connectors/common => packages/kbn-search-connectors/constants}/doc_links.ts (90%) create mode 100644 packages/kbn-search-connectors/constants/index.ts create mode 100644 packages/kbn-search-connectors/types/connector_definition.ts delete mode 100644 x-pack/plugins/search_connectors/common/constants.ts delete mode 100644 x-pack/plugins/search_connectors/common/types.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 7936e52ccbf18..432211d395f61 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -138,7 +138,7 @@ pageLoadAssetSize: screenshotMode: 17856 screenshotting: 22870 searchAssistant: 19831 - searchConnectors: 30000 + searchConnectors: 65000 searchHomepage: 19831 searchIndices: 20519 searchInferenceEndpoints: 20470 diff --git a/x-pack/plugins/search_connectors/common/connectors.ts b/packages/kbn-search-connectors/constants/connectors.ts similarity index 50% rename from x-pack/plugins/search_connectors/common/connectors.ts rename to packages/kbn-search-connectors/constants/connectors.ts index b0bc5564e9750..ad5c716234133 100644 --- a/x-pack/plugins/search_connectors/common/connectors.ts +++ b/packages/kbn-search-connectors/constants/connectors.ts @@ -1,42 +1,207 @@ /* * 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. + * 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 { i18n } from '@kbn/i18n'; +import { + ConnectorClientSideDefinition, + ConnectorServerSideDefinition, +} from '../types/connector_definition'; -export interface ConnectorServerSideDefinition { - categories?: string[]; - description?: string; - iconPath: string; - isBeta: boolean; - isNative: boolean; - isTechPreview?: boolean; - keywords: string[]; - name: string; - serviceType: string; -} +import { docLinks } from './doc_links'; + +// needs to be a function because, docLinks are only populated with actual +// documentation links in browser after SearchConnectorsPlugin starts +export const getConnectorsDict = (): Record => ({ + azure_blob_storage: { + docsUrl: docLinks.connectorsAzureBlobStorage, + externalAuthDocsUrl: 'https://learn.microsoft.com/azure/storage/common/authorize-data-access', + externalDocsUrl: 'https://learn.microsoft.com/azure/storage/blobs/', + platinumOnly: true, + }, + box: { + docsUrl: docLinks.connectorsBox, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + confluence: { + docsUrl: docLinks.connectorsConfluence, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + custom: { + docsUrl: docLinks.connectors, + externalAuthDocsUrl: '', + externalDocsUrl: '', + }, + dropbox: { + docsUrl: docLinks.connectorsDropbox, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + github: { + docsUrl: docLinks.connectorsGithub, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + gmail: { + docsUrl: docLinks.connectorsGmail, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + google_cloud_storage: { + docsUrl: docLinks.connectorsGoogleCloudStorage, + externalAuthDocsUrl: 'https://cloud.google.com/storage/docs/authentication', + externalDocsUrl: 'https://cloud.google.com/storage/docs', + platinumOnly: true, + }, + google_drive: { + docsUrl: docLinks.connectorsGoogleDrive, + externalAuthDocsUrl: 'https://cloud.google.com/iam/docs/service-account-overview', + externalDocsUrl: 'https://developers.google.com/drive', + platinumOnly: true, + }, + jira: { + docsUrl: docLinks.connectorsJira, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + microsoft_teams: { + docsUrl: docLinks.connectorsTeams, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + mongodb: { + docsUrl: docLinks.connectorsMongoDB, + externalAuthDocsUrl: 'https://www.mongodb.com/docs/atlas/app-services/authentication/', + externalDocsUrl: 'https://www.mongodb.com/docs/', + platinumOnly: true, + }, + mssql: { + docsUrl: docLinks.connectorsMicrosoftSQL, + externalAuthDocsUrl: + 'https://learn.microsoft.com/sql/relational-databases/security/authentication-access/getting-started-with-database-engine-permissions', + externalDocsUrl: 'https://learn.microsoft.com/sql/', + platinumOnly: true, + }, + mysql: { + docsUrl: docLinks.connectorsMySQL, + externalDocsUrl: 'https://dev.mysql.com/doc/', + platinumOnly: true, + }, + network_drive: { + docsUrl: docLinks.connectorsNetworkDrive, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + notion: { + docsUrl: docLinks.connectorsNotion, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + onedrive: { + docsUrl: docLinks.connectorsOneDrive, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + oracle: { + docsUrl: docLinks.connectorsOracle, + externalAuthDocsUrl: + 'https://docs.oracle.com/en/database/oracle/oracle-database/19/dbseg/index.html', + externalDocsUrl: 'https://docs.oracle.com/database/oracle/oracle-database/', + platinumOnly: true, + }, + outlook: { + docsUrl: docLinks.connectorsOutlook, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + postgresql: { + docsUrl: docLinks.connectorsPostgreSQL, + externalAuthDocsUrl: 'https://www.postgresql.org/docs/15/auth-methods.html', + externalDocsUrl: 'https://www.postgresql.org/docs/', + platinumOnly: true, + }, + redis: { + docsUrl: docLinks.connectorsRedis, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + s3: { + docsUrl: docLinks.connectorsS3, + externalAuthDocsUrl: 'https://docs.aws.amazon.com/s3/index.html', + externalDocsUrl: '', + platinumOnly: true, + }, + salesforce: { + docsUrl: docLinks.connectorsSalesforce, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + servicenow: { + docsUrl: docLinks.connectorsServiceNow, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + sharepoint_online: { + docsUrl: docLinks.connectorsSharepointOnline, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + sharepoint_server: { + docsUrl: docLinks.connectorsSharepoint, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + slack: { + docsUrl: docLinks.connectorsSlack, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, + zoom: { + docsUrl: docLinks.connectorsZoom, + externalAuthDocsUrl: '', + externalDocsUrl: '', + platinumOnly: true, + }, +}); /* The consumer should host these icons and transform the iconPath into something usable * Enterprise Search and Serverless Search do this right now */ - export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.azureBlob.description', - { - defaultMessage: 'Search over your content on Azure Blob Storage.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.azureBlob.description', { + defaultMessage: 'Search over your content on Azure Blob Storage.', + }), iconPath: 'azure_blob_storage.svg', isBeta: false, isNative: true, keywords: ['cloud', 'azure', 'blob', 's3', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.azureBlob.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.azureBlob.name', { defaultMessage: 'Azure Blob Storage', }), serviceType: 'azure_blob_storage', @@ -44,7 +209,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.confluence.description', + 'searchConnectors.content.nativeConnectors.confluence.description', { defaultMessage: 'Search over your content on Confluence Cloud.', } @@ -53,7 +218,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['confluence', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.confluence.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.confluence.name', { defaultMessage: 'Confluence Cloud & Server', }), serviceType: 'confluence', @@ -61,7 +226,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.confluenceDataCenter.description', + 'searchConnectors.content.nativeConnectors.confluenceDataCenter.description', { defaultMessage: 'Search over your content on Confluence Data Center.', } @@ -71,45 +236,36 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: true, keywords: ['confluence', 'data', 'center', 'connector'], - name: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.confluence_data_center.name', - { - defaultMessage: 'Confluence Data Center', - } - ), + name: i18n.translate('searchConnectors.content.nativeConnectors.confluence_data_center.name', { + defaultMessage: 'Confluence Data Center', + }), serviceType: 'confluence', }, { categories: ['search', 'elastic_stack', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.dropbox.description', - { - defaultMessage: 'Search over your files and folders stored on Dropbox.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.dropbox.description', { + defaultMessage: 'Search over your files and folders stored on Dropbox.', + }), iconPath: 'dropbox.svg', isBeta: false, isNative: true, isTechPreview: false, keywords: ['dropbox', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.dropbox.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.dropbox.name', { defaultMessage: 'Dropbox', }), serviceType: 'dropbox', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'jira'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.jira.description', - { - defaultMessage: 'Search over your content on Jira Cloud.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.jira.description', { + defaultMessage: 'Search over your content on Jira Cloud.', + }), iconPath: 'jira_cloud.svg', isBeta: false, isNative: true, keywords: ['jira', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.jira.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.jira.name', { defaultMessage: 'Jira Cloud', }), serviceType: 'jira', @@ -117,7 +273,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'jira'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.jiraServer.description', + 'searchConnectors.content.nativeConnectors.jiraServer.description', { defaultMessage: 'Search over your content on Jira Server.', } @@ -126,7 +282,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: false, keywords: ['jira', 'server', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.jiraServer.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.jiraServer.name', { defaultMessage: 'Jira Server', }), serviceType: 'jira', @@ -134,7 +290,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.jiraDataCenter.description', + 'searchConnectors.content.nativeConnectors.jiraDataCenter.description', { defaultMessage: 'Search over your content on Jira Data Center.', } @@ -144,24 +300,21 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isTechPreview: true, isNative: true, keywords: ['jira', 'data', 'center', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.jira_data_center.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.jira_data_center.name', { defaultMessage: 'Jira Data Center', }), serviceType: 'jira', }, { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.github.description', - { - defaultMessage: 'Search over your projects and repos on GitHub.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.github.description', { + defaultMessage: 'Search over your projects and repos on GitHub.', + }), iconPath: 'github.svg', isBeta: false, isNative: true, keywords: ['github', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.github.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.github.name', { defaultMessage: 'GitHub & GitHub Enterprise Server', }), serviceType: 'github', @@ -169,7 +322,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.googleCloud.description', + 'searchConnectors.content.nativeConnectors.googleCloud.description', { defaultMessage: 'Search over your content on Google Cloud Storage.', } @@ -178,7 +331,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['google', 'cloud', 'blob', 's3', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.googleCloud.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.googleCloud.name', { defaultMessage: 'Google Cloud Storage', }), serviceType: 'google_cloud_storage', @@ -186,7 +339,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.googleDrive.description', + 'searchConnectors.content.nativeConnectors.googleDrive.description', { defaultMessage: 'Search over your content on Google Drive.', } @@ -195,24 +348,21 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['google', 'drive', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.googleDrive.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.googleDrive.name', { defaultMessage: 'Google Drive', }), serviceType: 'google_drive', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.graphQL.description', - { - defaultMessage: 'Search over your content with GraphQL.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.graphQL.description', { + defaultMessage: 'Search over your content with GraphQL.', + }), iconPath: 'graphql.svg', isBeta: false, isNative: false, keywords: ['graphql', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.graphQL.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.graphQL.name', { defaultMessage: 'GraphQL', }), serviceType: 'graphql', @@ -220,58 +370,49 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ }, { categories: ['search', 'datastore', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.mongoDB.description', - { - defaultMessage: 'Search over your MongoDB content.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.mongoDB.description', { + defaultMessage: 'Search over your MongoDB content.', + }), iconPath: 'mongodb.svg', isBeta: false, isNative: true, keywords: ['mongo', 'mongodb', 'database', 'nosql', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.mongodb.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.mongodb.name', { defaultMessage: 'MongoDB', }), serviceType: 'mongodb', }, { categories: ['search', 'datastore', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.mysql.description', - { - defaultMessage: 'Search over your MySQL content.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.mysql.description', { + defaultMessage: 'Search over your MySQL content.', + }), iconPath: 'mysql.svg', isBeta: false, isNative: true, keywords: ['mysql', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.mysql.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.mysql.name', { defaultMessage: 'MySQL', }), serviceType: 'mysql', }, { categories: ['search', 'custom', 'elastic_stack', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.msSql.description', - { - defaultMessage: 'Search over your content on Microsoft SQL Server.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.msSql.description', { + defaultMessage: 'Search over your content on Microsoft SQL Server.', + }), iconPath: 'mssql.svg', isBeta: false, isNative: true, keywords: ['mssql', 'microsoft', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.microsoftSQL.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.microsoftSQL.name', { defaultMessage: 'Microsoft SQL', }), serviceType: 'mssql', }, { description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.netowkrDrive.description', + 'searchConnectors.content.nativeConnectors.netowkrDrive.description', { defaultMessage: 'Search over your Network Drive content.', } @@ -281,31 +422,28 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['network', 'drive', 'file', 'directory', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.networkDrive.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.networkDrive.name', { defaultMessage: 'Network drive', }), serviceType: 'network_drive', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.notion.description', - { - defaultMessage: 'Search over your content on Notion.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.notion.description', { + defaultMessage: 'Search over your content on Notion.', + }), iconPath: 'notion.svg', isBeta: false, isNative: true, keywords: ['notion', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.notion.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.notion.name', { defaultMessage: 'Notion', }), serviceType: 'notion', }, { description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.postgreSQL.description', + 'searchConnectors.content.nativeConnectors.postgreSQL.description', { defaultMessage: 'Search over your content on PostgreSQL.', } @@ -315,25 +453,22 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['postgresql', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.postgresql.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.postgresql.name', { defaultMessage: 'PostgreSQL', }), serviceType: 'postgresql', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.redis.description', - { - defaultMessage: 'Search over your content on Redis.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.redis.description', { + defaultMessage: 'Search over your content on Redis.', + }), iconPath: 'redis.svg', isBeta: false, isNative: false, isTechPreview: true, keywords: ['redis', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.redis.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.redis.name', { defaultMessage: 'Redis', }), serviceType: 'redis', @@ -341,7 +476,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.salesforce.description', + 'searchConnectors.content.nativeConnectors.salesforce.description', { defaultMessage: 'Search over your content on Salesforce.', } @@ -350,7 +485,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['salesforce', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.salesforce.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.salesforce.name', { defaultMessage: 'Salesforce', }), serviceType: 'salesforce', @@ -358,7 +493,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'datastore', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.salesforceSandbox.description', + 'searchConnectors.content.nativeConnectors.salesforceSandbox.description', { defaultMessage: 'Search over your content on Salesforce Sandbox.', } @@ -367,7 +502,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['salesforce', 'cloud', 'connector', 'sandbox'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.salesforceBox.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.salesforceBox.name', { defaultMessage: 'Salesforce Sandbox', }), serviceType: 'salesforce', @@ -375,7 +510,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.serviceNow.description', + 'searchConnectors.content.nativeConnectors.serviceNow.description', { defaultMessage: 'Search over your content on ServiceNow.', } @@ -385,7 +520,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: false, keywords: ['servicenow', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.serviceNow.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.serviceNow.name', { defaultMessage: 'ServiceNow', }), serviceType: 'servicenow', @@ -393,7 +528,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.description', + 'searchConnectors.content.nativeConnectors.sharepointOnline.description', { defaultMessage: 'Search over your content on SharePoint Online.', } @@ -403,24 +538,21 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: false, keywords: ['sharepoint', 'office365', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.sharepointOnline.name', { defaultMessage: 'Sharepoint Online', }), serviceType: 'sharepoint_online', }, { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.gmail.description', - { - defaultMessage: 'Search over your content on Gmail.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.gmail.description', { + defaultMessage: 'Search over your content on Gmail.', + }), iconPath: 'gmail.svg', isBeta: false, isNative: true, keywords: ['gmail', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.gmail.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.gmail.name', { defaultMessage: 'Gmail', }), serviceType: 'gmail', @@ -428,7 +560,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.description', + 'searchConnectors.content.nativeConnectors.openTextDocumentum.description', { defaultMessage: 'Search over your content on OpenText Documentum.', } @@ -438,50 +570,41 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: false, isTechPreview: true, keywords: ['opentext', 'documentum', 'connector'], - name: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.name', - { - defaultMessage: 'OpenText Documentum', - } - ), + name: i18n.translate('searchConnectors.content.nativeConnectors.openTextDocumentum.name', { + defaultMessage: 'OpenText Documentum', + }), serviceType: 'opentext_documentum', }, { categories: ['search', 'elastic_stack', 'custom', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.oracle.description', - { - defaultMessage: 'Search over your content on Oracle.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.oracle.description', { + defaultMessage: 'Search over your content on Oracle.', + }), iconPath: 'oracle.svg', isBeta: false, isNative: true, keywords: ['oracle', 'sql', 'database', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.oracle.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.oracle.name', { defaultMessage: 'Oracle', }), serviceType: 'oracle', }, { categories: ['search', 'elastic_stack', 'custom', 'datastore', 'connector', 'connector_client'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.oneDrive.description', - { - defaultMessage: 'Search over your content on OneDrive.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.oneDrive.description', { + defaultMessage: 'Search over your content on OneDrive.', + }), iconPath: 'onedrive.svg', isBeta: false, isNative: true, keywords: ['network', 'drive', 'file', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.oneDrive.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.oneDrive.name', { defaultMessage: 'OneDrive', }), serviceType: 'onedrive', }, { - description: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.s3.description', { + description: i18n.translate('searchConnectors.content.nativeConnectors.s3.description', { defaultMessage: 'Search over your content on Amazon S3.', }), categories: ['search', 'datastore', 'elastic_stack', 'connector', 'connector_client'], @@ -489,25 +612,22 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: true, keywords: ['s3', 'cloud', 'amazon', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.s3.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.s3.name', { defaultMessage: 'S3', }), serviceType: 's3', }, { - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.slack.description', - { - defaultMessage: 'Search over your content on Slack.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.slack.description', { + defaultMessage: 'Search over your content on Slack.', + }), categories: ['search', 'elastic_stack', 'connector', 'connector_client'], iconPath: 'slack.svg', isBeta: false, isNative: true, isTechPreview: true, keywords: ['slack', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.slack.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.slack.name', { defaultMessage: 'Slack', }), serviceType: 'slack', @@ -515,7 +635,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.sharepointServer.description', + 'searchConnectors.content.nativeConnectors.sharepointServer.description', { defaultMessage: 'Search over your content on SharePoint Server.', } @@ -525,14 +645,14 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: false, keywords: ['sharepoint', 'cloud', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.sharepointServer.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.sharepointServer.name', { defaultMessage: 'Sharepoint Server', }), serviceType: 'sharepoint_server', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'box'], - description: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.box.description', { + description: i18n.translate('searchConnectors.content.nativeConnectors.box.description', { defaultMessage: 'Search over your content on Box.', }), iconPath: 'box.svg', @@ -540,60 +660,51 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isNative: true, isTechPreview: true, keywords: ['cloud', 'box'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.box.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.box.name', { defaultMessage: 'Box', }), serviceType: 'box', }, { - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.outlook.description', - { - defaultMessage: 'Search over your content on Outlook.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.outlook.description', { + defaultMessage: 'Search over your content on Outlook.', + }), categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'outlook'], iconPath: 'outlook.svg', isBeta: false, isNative: true, keywords: ['outlook', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.outlook.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.outlook.name', { defaultMessage: 'Outlook', }), serviceType: 'outlook', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'teams'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.teams.description', - { - defaultMessage: 'Search over your content on Teams.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.teams.description', { + defaultMessage: 'Search over your content on Teams.', + }), iconPath: 'teams.svg', isBeta: false, isNative: true, isTechPreview: true, keywords: ['teams', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.teams.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.teams.name', { defaultMessage: 'Teams', }), serviceType: 'microsoft_teams', }, { categories: ['search', 'elastic_stack', 'custom', 'connector', 'connector_client', 'zoom'], - description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.zoom.description', - { - defaultMessage: 'Search over your content on Zoom.', - } - ), + description: i18n.translate('searchConnectors.content.nativeConnectors.zoom.description', { + defaultMessage: 'Search over your content on Zoom.', + }), iconPath: 'zoom.svg', isBeta: false, isNative: true, isTechPreview: true, keywords: ['zoom', 'connector'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.zoom.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.zoom.name', { defaultMessage: 'Zoom', }), serviceType: 'zoom', @@ -601,7 +712,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ { categories: ['search', 'custom', 'elastic_stack', 'connector', 'connector_client'], description: i18n.translate( - 'searchConnectorsPlugin.content.nativeConnectors.customConnector.description', + 'searchConnectors.content.nativeConnectors.customConnector.description', { defaultMessage: 'Search over data stored on custom data sources.', } @@ -610,7 +721,7 @@ export const CONNECTOR_DEFINITIONS: ConnectorServerSideDefinition[] = [ isBeta: false, isNative: false, keywords: ['custom', 'connector', 'code'], - name: i18n.translate('searchConnectorsPlugin.content.nativeConnectors.customConnector.name', { + name: i18n.translate('searchConnectors.content.nativeConnectors.customConnector.name', { defaultMessage: 'Customized connector', }), serviceType: '', diff --git a/x-pack/plugins/search_connectors/common/doc_links.ts b/packages/kbn-search-connectors/constants/doc_links.ts similarity index 90% rename from x-pack/plugins/search_connectors/common/doc_links.ts rename to packages/kbn-search-connectors/constants/doc_links.ts index 0c5edc1a07ca7..db4dc3870e5c4 100644 --- a/x-pack/plugins/search_connectors/common/doc_links.ts +++ b/packages/kbn-search-connectors/constants/doc_links.ts @@ -1,8 +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. + * 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 { DocLinks } from '@kbn/doc-links'; diff --git a/packages/kbn-search-connectors/constants/index.ts b/packages/kbn-search-connectors/constants/index.ts new file mode 100644 index 0000000000000..6019d3d61be2f --- /dev/null +++ b/packages/kbn-search-connectors/constants/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 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". + */ + +export * from './connectors'; +export * from './doc_links'; diff --git a/packages/kbn-search-connectors/index.ts b/packages/kbn-search-connectors/index.ts index 2418d0f4d557d..5f45a1bd8ffb6 100644 --- a/packages/kbn-search-connectors/index.ts +++ b/packages/kbn-search-connectors/index.ts @@ -16,6 +16,7 @@ export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-'; export const CRAWLER_SERVICE_TYPE = 'elastic-crawler'; export * from './components'; +export * from './constants'; export * from './lib'; export * from './types'; export * from './utils'; diff --git a/packages/kbn-search-connectors/tsconfig.json b/packages/kbn-search-connectors/tsconfig.json index cb54e57748e94..4aebaeb1fcb13 100644 --- a/packages/kbn-search-connectors/tsconfig.json +++ b/packages/kbn-search-connectors/tsconfig.json @@ -25,5 +25,6 @@ "@kbn/i18n-react", "@kbn/test-jest-helpers", "@kbn/std", + "@kbn/doc-links", ] } diff --git a/packages/kbn-search-connectors/types/connector_definition.ts b/packages/kbn-search-connectors/types/connector_definition.ts new file mode 100644 index 0000000000000..a2dccf6554959 --- /dev/null +++ b/packages/kbn-search-connectors/types/connector_definition.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 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". + */ + +export interface ConnectorClientSideDefinition { + docsUrl?: string; + externalAuthDocsUrl?: string; + externalDocsUrl: string; + platinumOnly?: boolean; +} + +export interface ConnectorServerSideDefinition { + categories?: string[]; + description?: string; + iconPath: string; + isBeta: boolean; + isNative: boolean; + isTechPreview?: boolean; + keywords: string[]; + name: string; + serviceType: string; +} + +export type ConnectorDefinition = ConnectorClientSideDefinition & ConnectorServerSideDefinition; diff --git a/packages/kbn-search-connectors/types/index.ts b/packages/kbn-search-connectors/types/index.ts index ca5c483ab51df..aaf98748cfcbd 100644 --- a/packages/kbn-search-connectors/types/index.ts +++ b/packages/kbn-search-connectors/types/index.ts @@ -7,6 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +export * from './connector_definition'; export * from './connectors'; export * from './connectors_api'; export * from './connector_stats'; diff --git a/packages/kbn-search-connectors/types/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts index f9719cb84b801..c5ef7beab0ba5 100644 --- a/packages/kbn-search-connectors/types/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -11,54 +11,64 @@ import { i18n } from '@kbn/i18n'; import { DisplayType, FeatureName, FieldType, NativeConnector } from './connectors'; -const USERNAME_LABEL = i18n.translate('searchConnectors.nativeConnectors.usernameLabel', { +// assigning these to a local var significantly improves bundle size +// because it reduces references to the imported modules. +const { translate } = i18n; +const { TEXTBOX, TEXTAREA, NUMERIC, TOGGLE, DROPDOWN } = DisplayType; +const { + SYNC_RULES, + INCREMENTAL_SYNC, + DOCUMENT_LEVEL_SECURITY, + FILTERING_ADVANCED_CONFIG, + FILTERING_RULES, +} = FeatureName; +const { STRING, LIST, INTEGER, BOOLEAN } = FieldType; + +const USERNAME_LABEL = translate('searchConnectors.nativeConnectors.usernameLabel', { defaultMessage: 'Username', }); -const PASSWORD_LABEL = i18n.translate('searchConnectors.nativeConnectors.passwordLabel', { +const PASSWORD_LABEL = translate('searchConnectors.nativeConnectors.passwordLabel', { defaultMessage: 'Password', }); -const ENABLE_SSL_LABEL = i18n.translate('searchConnectors.nativeConnectors.enableSSL.label', { +const ENABLE_SSL_LABEL = translate('searchConnectors.nativeConnectors.enableSSL.label', { defaultMessage: 'Enable SSL', }); -const SSL_CERTIFICATE_LABEL = i18n.translate( - 'searchConnectors.nativeConnectors.sslCertificate.label', - { - defaultMessage: 'SSL certificate', - } -); +const SSL_CERTIFICATE_LABEL = translate('searchConnectors.nativeConnectors.sslCertificate.label', { + defaultMessage: 'SSL certificate', +}); -const RETRIES_PER_REQUEST_LABEL = i18n.translate( +const RETRIES_PER_REQUEST_LABEL = translate( 'searchConnectors.nativeConnectors.retriesPerRequest.label', { defaultMessage: 'Retries per request', } ); -const ADVANCED_RULES_IGNORED_LABEL = i18n.translate( +const ADVANCED_RULES_IGNORED_LABEL = translate( 'searchConnectors.nativeConnectors.advancedRulesIgnored.label', { defaultMessage: 'This configurable field is ignored when Advanced Sync Rules are used.', } ); -const MAX_CONCURRENT_DOWNLOADS_LABEL = i18n.translate( +const MAX_CONCURRENT_DOWNLOADS_LABEL = translate( 'searchConnectors.nativeConnectors.nativeConnectors.maximumConcurrentLabel', { defaultMessage: 'Maximum concurrent downloads', } ); -const USE_TEXT_EXTRACTION_SERVICE_LABEL = i18n.translate( +const USE_TEXT_EXTRACTION_SERVICE_LABEL = translate( 'searchConnectors.nativeConnectors.textExtractionService.label', { defaultMessage: 'Use text extraction service', } ); -const USE_TEXT_EXTRACTION_SERVICE_TOOLTIP = i18n.translate( +const USE_TEXT_EXTRACTION_SERVICE_TOOLTIP = translate( 'searchConnectors.nativeConnectors.textExtractionService.tooltip', { defaultMessage: @@ -67,7 +77,7 @@ const USE_TEXT_EXTRACTION_SERVICE_TOOLTIP = i18n.translate( } ); -const ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL = i18n.translate( +const ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL = translate( 'searchConnectors.nativeConnectors.enableDLS.label', { defaultMessage: 'Enable document level security', @@ -75,21 +85,21 @@ const ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL = i18n.translate( ); const getEnableDocumentLevelSecurityTooltip = (serviceName: string) => - i18n.translate('searchConnectors.nativeConnectors.enableDLS.tooltip', { + translate('searchConnectors.nativeConnectors.enableDLS.tooltip', { defaultMessage: 'Document level security ensures identities and permissions set in {serviceName} are maintained in Elasticsearch. This enables you to restrict and personalize read-access users and groups have to documents in this index. Access control syncs ensure this metadata is kept up to date in your Elasticsearch documents.', values: { serviceName }, }); -const DATABASE_LABEL = i18n.translate('searchConnectors.nativeConnectors.databaseLabel', { +const DATABASE_LABEL = translate('searchConnectors.nativeConnectors.databaseLabel', { defaultMessage: 'Database', }); -const SCHEMA_LABEL = i18n.translate('searchConnectors.nativeConnectors.schemaLabel', { +const SCHEMA_LABEL = translate('searchConnectors.nativeConnectors.schemaLabel', { defaultMessage: 'Schema', }); -const PORT_LABEL = i18n.translate('searchConnectors.nativeConnectors.portLabel', { +const PORT_LABEL = translate('searchConnectors.nativeConnectors.portLabel', { defaultMessage: 'Port', }); @@ -103,19 +113,16 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record Account -> Settings -> Customer Id', }), - type: FieldType.STRING, + type: STRING, ui_restrictions: [], validations: [], value: '', @@ -1355,24 +1341,21 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record => ({ - azure_blob_storage: { - docsUrl: docLinks.connectorsAzureBlobStorage, - externalAuthDocsUrl: 'https://learn.microsoft.com/azure/storage/common/authorize-data-access', - externalDocsUrl: 'https://learn.microsoft.com/azure/storage/blobs/', - platinumOnly: true, - }, - box: { - docsUrl: docLinks.connectorsBox, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - confluence: { - docsUrl: docLinks.connectorsConfluence, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - custom: { - docsUrl: docLinks.connectors, - externalAuthDocsUrl: '', - externalDocsUrl: '', - }, - dropbox: { - docsUrl: docLinks.connectorsDropbox, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - github: { - docsUrl: docLinks.connectorsGithub, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - gmail: { - docsUrl: docLinks.connectorsGmail, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - google_cloud_storage: { - docsUrl: docLinks.connectorsGoogleCloudStorage, - externalAuthDocsUrl: 'https://cloud.google.com/storage/docs/authentication', - externalDocsUrl: 'https://cloud.google.com/storage/docs', - platinumOnly: true, - }, - google_drive: { - docsUrl: docLinks.connectorsGoogleDrive, - externalAuthDocsUrl: 'https://cloud.google.com/iam/docs/service-account-overview', - externalDocsUrl: 'https://developers.google.com/drive', - platinumOnly: true, - }, - jira: { - docsUrl: docLinks.connectorsJira, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - microsoft_teams: { - docsUrl: docLinks.connectorsTeams, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - mongodb: { - docsUrl: docLinks.connectorsMongoDB, - externalAuthDocsUrl: 'https://www.mongodb.com/docs/atlas/app-services/authentication/', - externalDocsUrl: 'https://www.mongodb.com/docs/', - platinumOnly: true, - }, - mssql: { - docsUrl: docLinks.connectorsMicrosoftSQL, - externalAuthDocsUrl: - 'https://learn.microsoft.com/sql/relational-databases/security/authentication-access/getting-started-with-database-engine-permissions', - externalDocsUrl: 'https://learn.microsoft.com/sql/', - platinumOnly: true, - }, - mysql: { - docsUrl: docLinks.connectorsMySQL, - externalDocsUrl: 'https://dev.mysql.com/doc/', - platinumOnly: true, - }, - network_drive: { - docsUrl: docLinks.connectorsNetworkDrive, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - notion: { - docsUrl: docLinks.connectorsNotion, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - onedrive: { - docsUrl: docLinks.connectorsOneDrive, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - oracle: { - docsUrl: docLinks.connectorsOracle, - externalAuthDocsUrl: - 'https://docs.oracle.com/en/database/oracle/oracle-database/19/dbseg/index.html', - externalDocsUrl: 'https://docs.oracle.com/database/oracle/oracle-database/', - platinumOnly: true, - }, - outlook: { - docsUrl: docLinks.connectorsOutlook, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - postgresql: { - docsUrl: docLinks.connectorsPostgreSQL, - externalAuthDocsUrl: 'https://www.postgresql.org/docs/15/auth-methods.html', - externalDocsUrl: 'https://www.postgresql.org/docs/', - platinumOnly: true, - }, - redis: { - docsUrl: docLinks.connectorsRedis, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - s3: { - docsUrl: docLinks.connectorsS3, - externalAuthDocsUrl: 'https://docs.aws.amazon.com/s3/index.html', - externalDocsUrl: '', - platinumOnly: true, - }, - salesforce: { - docsUrl: docLinks.connectorsSalesforce, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - servicenow: { - docsUrl: docLinks.connectorsServiceNow, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - sharepoint_online: { - docsUrl: docLinks.connectorsSharepointOnline, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - sharepoint_server: { - docsUrl: docLinks.connectorsSharepoint, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - slack: { - docsUrl: docLinks.connectorsSlack, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, - zoom: { - docsUrl: docLinks.connectorsZoom, - externalAuthDocsUrl: '', - externalDocsUrl: '', - platinumOnly: true, - }, -}); diff --git a/x-pack/plugins/search_connectors/common/lib/connector_types.ts b/x-pack/plugins/search_connectors/common/lib/connector_types.ts index 387b405b45774..32eff7d676a1d 100644 --- a/x-pack/plugins/search_connectors/common/lib/connector_types.ts +++ b/x-pack/plugins/search_connectors/common/lib/connector_types.ts @@ -6,10 +6,12 @@ */ import type { IStaticAssets } from '@kbn/core-http-browser'; -import { ConnectorServerSideDefinition, CONNECTOR_DEFINITIONS } from '../connectors'; -import { getConnectorsDict } from '../constants'; - -import { ConnectorDefinition } from '../types'; +import { + CONNECTOR_DEFINITIONS, + ConnectorDefinition, + ConnectorServerSideDefinition, + getConnectorsDict, +} from '@kbn/search-connectors'; // used on server and in browser before plugin start when we don't have docLinks yet export function getConnectorTypes(staticAssets: IStaticAssets): ConnectorServerSideDefinition[] { diff --git a/x-pack/plugins/search_connectors/common/types.ts b/x-pack/plugins/search_connectors/common/types.ts deleted file mode 100644 index 9d5049895b963..0000000000000 --- a/x-pack/plugins/search_connectors/common/types.ts +++ /dev/null @@ -1,17 +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 { ConnectorServerSideDefinition } from './connectors'; - -export interface ConnectorClientSideDefinition { - docsUrl?: string; - externalAuthDocsUrl?: string; - externalDocsUrl: string; - platinumOnly?: boolean; -} - -export type ConnectorDefinition = ConnectorClientSideDefinition & ConnectorServerSideDefinition; diff --git a/x-pack/plugins/search_connectors/public/index.ts b/x-pack/plugins/search_connectors/public/index.ts index 77afb7b9f82d8..6a59a98b068f4 100644 --- a/x-pack/plugins/search_connectors/public/index.ts +++ b/x-pack/plugins/search_connectors/public/index.ts @@ -13,4 +13,3 @@ export function plugin() { } export type { SearchConnectorsPluginSetup, SearchConnectorsPluginStart } from './types'; -export type { ConnectorDefinition } from '../common/types'; diff --git a/x-pack/plugins/search_connectors/public/plugin.ts b/x-pack/plugins/search_connectors/public/plugin.ts index 830d9d3e94c1e..cc86709121ab0 100644 --- a/x-pack/plugins/search_connectors/public/plugin.ts +++ b/x-pack/plugins/search_connectors/public/plugin.ts @@ -6,7 +6,7 @@ */ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import { docLinks } from '../common/doc_links'; +import { docLinks } from '@kbn/search-connectors'; import { getConnectorFullTypes, getConnectorTypes } from '../common/lib/connector_types'; import { SearchConnectorsPluginSetup, diff --git a/x-pack/plugins/search_connectors/public/types.ts b/x-pack/plugins/search_connectors/public/types.ts index a86ca30170fce..ec77fb403af78 100644 --- a/x-pack/plugins/search_connectors/public/types.ts +++ b/x-pack/plugins/search_connectors/public/types.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { ConnectorServerSideDefinition } from '../common/connectors'; -import { ConnectorDefinition } from '../common/types'; +import { ConnectorDefinition, ConnectorServerSideDefinition } from '@kbn/search-connectors'; /* eslint-disable @typescript-eslint/no-empty-interface */ diff --git a/x-pack/plugins/search_connectors/server/index.ts b/x-pack/plugins/search_connectors/server/index.ts index 304eb460ca7d6..9a52237740d16 100644 --- a/x-pack/plugins/search_connectors/server/index.ts +++ b/x-pack/plugins/search_connectors/server/index.ts @@ -18,4 +18,3 @@ export function plugin(initializerContext: PluginInitializerContext) { } export type { SearchConnectorsPluginSetup, SearchConnectorsPluginStart } from './types'; -export type { CONNECTOR_DEFINITIONS, ConnectorServerSideDefinition } from '../common/connectors'; diff --git a/x-pack/plugins/search_connectors/server/plugin.ts b/x-pack/plugins/search_connectors/server/plugin.ts index 56f78638da2d7..fe73afae20b9a 100644 --- a/x-pack/plugins/search_connectors/server/plugin.ts +++ b/x-pack/plugins/search_connectors/server/plugin.ts @@ -6,7 +6,7 @@ */ import type { PluginInitializerContext, Plugin, CoreSetup } from '@kbn/core/server'; -import { ConnectorServerSideDefinition } from '../common/connectors'; +import { ConnectorServerSideDefinition } from '@kbn/search-connectors'; import { getConnectorTypes } from '../common/lib/connector_types'; import type { SearchConnectorsPluginSetup as SearchConnectorsPluginSetup, diff --git a/x-pack/plugins/search_connectors/server/types.ts b/x-pack/plugins/search_connectors/server/types.ts index c08b562ec8a5f..36b5aa877fd1e 100644 --- a/x-pack/plugins/search_connectors/server/types.ts +++ b/x-pack/plugins/search_connectors/server/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ConnectorServerSideDefinition } from '../common/connectors'; +import { ConnectorServerSideDefinition } from '@kbn/search-connectors'; /* eslint-disable @typescript-eslint/no-empty-interface */ diff --git a/x-pack/plugins/search_connectors/tsconfig.json b/x-pack/plugins/search_connectors/tsconfig.json index 9da933a758aec..040c873fe0353 100644 --- a/x-pack/plugins/search_connectors/tsconfig.json +++ b/x-pack/plugins/search_connectors/tsconfig.json @@ -17,8 +17,7 @@ "kbn_references": [ "@kbn/core", "@kbn/config-schema", - "@kbn/doc-links", "@kbn/core-http-browser", - "@kbn/i18n", + "@kbn/search-connectors", ] } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 1521e3e251702..35fa0b829cbee 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -6393,11 +6393,61 @@ "searchConnectors.content.indices.connectorScheduling.schedulePanel.contentSync.description": "Récupérez du contenu pour créer ou mettre à jour vos documents Elasticsearch.", "searchConnectors.content.indices.connectorScheduling.schedulePanel.contentSync.title": "Synchronisation de contenu", "searchConnectors.content.indices.connectorScheduling.switch.label": "Activé", + "searchConnectors.content.nativeConnectors.azureBlob.description": "Effectuez des recherches sur votre contenu sur Stockage Blob Azure.", + "searchConnectors.content.nativeConnectors.azureBlob.name": "Stockage Blob Azure", + "searchConnectors.content.nativeConnectors.box.description": "Effectuez des recherches sur votre contenu dans Box.", + "searchConnectors.content.nativeConnectors.box.name": "Box", + "searchConnectors.content.nativeConnectors.confluence_data_center.name": "Centre de données Confluence", + "searchConnectors.content.nativeConnectors.confluence.description": "Effectuez des recherches sur votre contenu dans Confluence Cloud.", + "searchConnectors.content.nativeConnectors.confluence.name": "Confluence Cloud & Server", + "searchConnectors.content.nativeConnectors.confluenceDataCenter.description": "Effectuez des recherches sur votre contenu dans le centre de données Confluence.", + "searchConnectors.content.nativeConnectors.customConnector.description": "Effectuez des recherches sur des données stockées dans des sources de données personnalisées.", + "searchConnectors.content.nativeConnectors.customConnector.name": "Connecteur personnalisé", + "searchConnectors.content.nativeConnectors.dropbox.description": "Effectuez des recherches dans vos fichiers et dossiers stockés sur Dropbox.", + "searchConnectors.content.nativeConnectors.dropbox.name": "Dropbox", + "searchConnectors.content.nativeConnectors.github.description": "Effectuez des recherches sur vos projets et référentiels sur GitHub.", + "searchConnectors.content.nativeConnectors.github.name": "Serveurs GitHub & GitHub Enterprise", + "searchConnectors.content.nativeConnectors.gmail.description": "Effectuez des recherches sur votre contenu dans Gmail.", + "searchConnectors.content.nativeConnectors.gmail.name": "Gmail", + "searchConnectors.content.nativeConnectors.googleCloud.description": "Effectuez des recherches sur votre contenu sur Google Cloud Storage.", "searchConnectors.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", + "searchConnectors.content.nativeConnectors.googleDrive.description": "Effectuez des recherches sur votre contenu sur Google Drive.", + "searchConnectors.content.nativeConnectors.googleDrive.name": "Google Drive", + "searchConnectors.content.nativeConnectors.graphQL.description": "Effectuez des recherches dans votre contenu avec GraphQL.", + "searchConnectors.content.nativeConnectors.graphQL.name": "GraphQL", + "searchConnectors.content.nativeConnectors.jira_data_center.name": "Centre de données Jira", + "searchConnectors.content.nativeConnectors.jira.description": "Effectuez des recherches sur votre contenu dans Jira Cloud.", + "searchConnectors.content.nativeConnectors.jira.name": "Jira Cloud", + "searchConnectors.content.nativeConnectors.jiraDataCenter.description": "Effectuez des recherches sur votre contenu dans le centre de données Jira.", + "searchConnectors.content.nativeConnectors.jiraServer.description": "Effectuez des recherches sur votre contenu dans le serveur Jira.", + "searchConnectors.content.nativeConnectors.jiraServer.name": "Serveur Jira", + "searchConnectors.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", + "searchConnectors.content.nativeConnectors.mongoDB.description": "Effectuez des recherches sur votre contenu dans MongoDB.", + "searchConnectors.content.nativeConnectors.mongodb.name": "MongoDB", + "searchConnectors.content.nativeConnectors.msSql.description": "Effectuez des recherches sur votre contenu sur Microsoft SQL Server.", + "searchConnectors.content.nativeConnectors.mysql.description": "Effectuez des recherches sur votre contenu dans MySQL.", + "searchConnectors.content.nativeConnectors.mysql.name": "MySQL", + "searchConnectors.content.nativeConnectors.netowkrDrive.description": "Effectuez des recherches sur le contenu de votre lecteur réseau.", + "searchConnectors.content.nativeConnectors.networkDrive.name": "Lecteur réseau", + "searchConnectors.content.nativeConnectors.notion.description": "Effectuez des recherches sur votre contenu dans Notion.", + "searchConnectors.content.nativeConnectors.notion.name": "Notion", + "searchConnectors.content.nativeConnectors.oneDrive.description": "Effectuez des recherches sur votre contenu dans OneDrive.", + "searchConnectors.content.nativeConnectors.oneDrive.name": "OneDrive", + "searchConnectors.content.nativeConnectors.openTextDocumentum.description": "Recherchez votre contenu sur OpenText Documentum.", + "searchConnectors.content.nativeConnectors.openTextDocumentum.name": "OpenText Documentum", + "searchConnectors.content.nativeConnectors.oracle.description": "Effectuez des recherches sur votre contenu dans Oracle.", + "searchConnectors.content.nativeConnectors.oracle.name": "Oracle", + "searchConnectors.content.nativeConnectors.outlook.description": "Effectuez des recherches sur votre contenu dans Outlook.", + "searchConnectors.content.nativeConnectors.outlook.name": "Outlook", + "searchConnectors.content.nativeConnectors.postgreSQL.description": "Effectuez des recherches sur votre contenu dans PostgreSQL.", + "searchConnectors.content.nativeConnectors.postgresql.name": "PostgreSQL", + "searchConnectors.content.nativeConnectors.redis.description": "Effectuez des recherches sur votre contenu dans Redis.", + "searchConnectors.content.nativeConnectors.redis.name": "Redis", "searchConnectors.content.nativeConnectors.s3.accessKey.label": "ID de clé d'accès AWS", "searchConnectors.content.nativeConnectors.s3.buckets.label": "Compartiments AWS", "searchConnectors.content.nativeConnectors.s3.buckets.tooltip": "Les compartiments AWS sont ignorés lorsque des règles de synchronisation avancées sont appliquées.", "searchConnectors.content.nativeConnectors.s3.connectTimeout.label": "Délai d'attente de connexion", + "searchConnectors.content.nativeConnectors.s3.description": "Effectuez des recherches sur votre contenu dans Amazon S3.", "searchConnectors.content.nativeConnectors.s3.maxAttempts.label": "Nombre maximum de nouvelles tentatives", "searchConnectors.content.nativeConnectors.s3.maxPageSize.label": "Taille maximum de la page", "searchConnectors.content.nativeConnectors.s3.name": "S3", @@ -6407,9 +6457,24 @@ "searchConnectors.content.nativeConnectors.salesforce.clientId.tooltip": "L'ID client de votre application connectée utilisant le protocole OAuth2. Également appelé \"clé consommateur\"", "searchConnectors.content.nativeConnectors.salesforce.clientSecret.label": "Identifiant client secret", "searchConnectors.content.nativeConnectors.salesforce.clientSecret.tooltip": "L'identifiant client secret de votre application connectée utilisant le protocole OAuth2. Également appelé \"secret consommateur\"", + "searchConnectors.content.nativeConnectors.salesforce.description": "Effectuez des recherches sur votre contenu dans Salesforce.", "searchConnectors.content.nativeConnectors.salesforce.domain.label": "Domaine", "searchConnectors.content.nativeConnectors.salesforce.domain.tooltip": "Le domaine de votre instance Salesforce. Si votre URL Salesforce est \"https://foo.salesforce.com\", le domaine est \"foo\".", "searchConnectors.content.nativeConnectors.salesforce.name": "Salesforce", + "searchConnectors.content.nativeConnectors.salesforceBox.name": "Sandbox Salesforce", + "searchConnectors.content.nativeConnectors.salesforceSandbox.description": "Effectuez des recherches sur votre contenu dans Salesforce Sandbox.", + "searchConnectors.content.nativeConnectors.serviceNow.description": "Effectuez des recherches sur votre contenu dans ServiceNow.", + "searchConnectors.content.nativeConnectors.serviceNow.name": "ServiceNow", + "searchConnectors.content.nativeConnectors.sharepointOnline.description": "Effectuez des recherches sur votre contenu dans SharePoint Online.", + "searchConnectors.content.nativeConnectors.sharepointOnline.name": "SharePoint en ligne", + "searchConnectors.content.nativeConnectors.sharepointServer.description": "Effectuez des recherches sur votre contenu dans Serveur SharePoint.", + "searchConnectors.content.nativeConnectors.sharepointServer.name": "Serveur SharePoint", + "searchConnectors.content.nativeConnectors.slack.description": "Effectuez des recherches sur votre contenu dans Slack.", + "searchConnectors.content.nativeConnectors.slack.name": "Slack", + "searchConnectors.content.nativeConnectors.teams.description": "Effectuez des recherches sur votre contenu dans Teams.", + "searchConnectors.content.nativeConnectors.teams.name": "Équipes", + "searchConnectors.content.nativeConnectors.zoom.description": "Effectuez des recherches sur votre contenu dans Zoom.", + "searchConnectors.content.nativeConnectors.zoom.name": "Effectuer un zoom", "searchConnectors.cronEditor.cronDaily.fieldHour.textAtLabel": "À", "searchConnectors.cronEditor.cronDaily.fieldTimeLabel": "Heure", "searchConnectors.cronEditor.cronDaily.hourSelectLabel": "Heure", @@ -6783,74 +6848,6 @@ "searchConnectors.syncStatus.inProgress": "Synchronisation en cours", "searchConnectors.syncStatus.pending": "Synchronisation en attente", "searchConnectors.syncStatus.suspended": "Synchronisation suspendue", - "searchConnectorsPlugin.content.nativeConnectors.azureBlob.description": "Effectuez des recherches sur votre contenu sur Stockage Blob Azure.", - "searchConnectorsPlugin.content.nativeConnectors.azureBlob.name": "Stockage Blob Azure", - "searchConnectorsPlugin.content.nativeConnectors.box.description": "Effectuez des recherches sur votre contenu dans Box.", - "searchConnectorsPlugin.content.nativeConnectors.box.name": "Box", - "searchConnectorsPlugin.content.nativeConnectors.confluence_data_center.name": "Centre de données Confluence", - "searchConnectorsPlugin.content.nativeConnectors.confluence.description": "Effectuez des recherches sur votre contenu dans Confluence Cloud.", - "searchConnectorsPlugin.content.nativeConnectors.confluence.name": "Confluence Cloud & Server", - "searchConnectorsPlugin.content.nativeConnectors.confluenceDataCenter.description": "Effectuez des recherches sur votre contenu dans le centre de données Confluence.", - "searchConnectorsPlugin.content.nativeConnectors.customConnector.description": "Effectuez des recherches sur des données stockées dans des sources de données personnalisées.", - "searchConnectorsPlugin.content.nativeConnectors.customConnector.name": "Connecteur personnalisé", - "searchConnectorsPlugin.content.nativeConnectors.dropbox.description": "Effectuez des recherches dans vos fichiers et dossiers stockés sur Dropbox.", - "searchConnectorsPlugin.content.nativeConnectors.dropbox.name": "Dropbox", - "searchConnectorsPlugin.content.nativeConnectors.github.description": "Effectuez des recherches sur vos projets et référentiels sur GitHub.", - "searchConnectorsPlugin.content.nativeConnectors.github.name": "Serveurs GitHub & GitHub Enterprise", - "searchConnectorsPlugin.content.nativeConnectors.gmail.description": "Effectuez des recherches sur votre contenu dans Gmail.", - "searchConnectorsPlugin.content.nativeConnectors.gmail.name": "Gmail", - "searchConnectorsPlugin.content.nativeConnectors.googleCloud.description": "Effectuez des recherches sur votre contenu sur Google Cloud Storage.", - "searchConnectorsPlugin.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", - "searchConnectorsPlugin.content.nativeConnectors.googleDrive.description": "Effectuez des recherches sur votre contenu sur Google Drive.", - "searchConnectorsPlugin.content.nativeConnectors.googleDrive.name": "Google Drive", - "searchConnectorsPlugin.content.nativeConnectors.graphQL.description": "Effectuez des recherches dans votre contenu avec GraphQL.", - "searchConnectorsPlugin.content.nativeConnectors.graphQL.name": "GraphQL", - "searchConnectorsPlugin.content.nativeConnectors.jira_data_center.name": "Centre de données Jira", - "searchConnectorsPlugin.content.nativeConnectors.jira.description": "Effectuez des recherches sur votre contenu dans Jira Cloud.", - "searchConnectorsPlugin.content.nativeConnectors.jira.name": "Jira Cloud", - "searchConnectorsPlugin.content.nativeConnectors.jiraDataCenter.description": "Effectuez des recherches sur votre contenu dans le centre de données Jira.", - "searchConnectorsPlugin.content.nativeConnectors.jiraServer.description": "Effectuez des recherches sur votre contenu dans le serveur Jira.", - "searchConnectorsPlugin.content.nativeConnectors.jiraServer.name": "Serveur Jira", - "searchConnectorsPlugin.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", - "searchConnectorsPlugin.content.nativeConnectors.mongoDB.description": "Effectuez des recherches sur votre contenu dans MongoDB.", - "searchConnectorsPlugin.content.nativeConnectors.mongodb.name": "MongoDB", - "searchConnectorsPlugin.content.nativeConnectors.msSql.description": "Effectuez des recherches sur votre contenu sur Microsoft SQL Server.", - "searchConnectorsPlugin.content.nativeConnectors.mysql.description": "Effectuez des recherches sur votre contenu dans MySQL.", - "searchConnectorsPlugin.content.nativeConnectors.mysql.name": "MySQL", - "searchConnectorsPlugin.content.nativeConnectors.netowkrDrive.description": "Effectuez des recherches sur le contenu de votre lecteur réseau.", - "searchConnectorsPlugin.content.nativeConnectors.networkDrive.name": "Lecteur réseau", - "searchConnectorsPlugin.content.nativeConnectors.notion.description": "Effectuez des recherches sur votre contenu dans Notion.", - "searchConnectorsPlugin.content.nativeConnectors.notion.name": "Notion", - "searchConnectorsPlugin.content.nativeConnectors.oneDrive.description": "Effectuez des recherches sur votre contenu dans OneDrive.", - "searchConnectorsPlugin.content.nativeConnectors.oneDrive.name": "OneDrive", - "searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.description": "Recherchez votre contenu sur OpenText Documentum.", - "searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.name": "OpenText Documentum", - "searchConnectorsPlugin.content.nativeConnectors.oracle.description": "Effectuez des recherches sur votre contenu dans Oracle.", - "searchConnectorsPlugin.content.nativeConnectors.oracle.name": "Oracle", - "searchConnectorsPlugin.content.nativeConnectors.outlook.description": "Effectuez des recherches sur votre contenu dans Outlook.", - "searchConnectorsPlugin.content.nativeConnectors.outlook.name": "Outlook", - "searchConnectorsPlugin.content.nativeConnectors.postgreSQL.description": "Effectuez des recherches sur votre contenu dans PostgreSQL.", - "searchConnectorsPlugin.content.nativeConnectors.postgresql.name": "PostgreSQL", - "searchConnectorsPlugin.content.nativeConnectors.redis.description": "Effectuez des recherches sur votre contenu dans Redis.", - "searchConnectorsPlugin.content.nativeConnectors.redis.name": "Redis", - "searchConnectorsPlugin.content.nativeConnectors.s3.description": "Effectuez des recherches sur votre contenu dans Amazon S3.", - "searchConnectorsPlugin.content.nativeConnectors.s3.name": "S3", - "searchConnectorsPlugin.content.nativeConnectors.salesforce.description": "Effectuez des recherches sur votre contenu dans Salesforce.", - "searchConnectorsPlugin.content.nativeConnectors.salesforce.name": "Salesforce", - "searchConnectorsPlugin.content.nativeConnectors.salesforceBox.name": "Sandbox Salesforce", - "searchConnectorsPlugin.content.nativeConnectors.salesforceSandbox.description": "Effectuez des recherches sur votre contenu dans Salesforce Sandbox.", - "searchConnectorsPlugin.content.nativeConnectors.serviceNow.description": "Effectuez des recherches sur votre contenu dans ServiceNow.", - "searchConnectorsPlugin.content.nativeConnectors.serviceNow.name": "ServiceNow", - "searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.description": "Effectuez des recherches sur votre contenu dans SharePoint Online.", - "searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.name": "SharePoint en ligne", - "searchConnectorsPlugin.content.nativeConnectors.sharepointServer.description": "Effectuez des recherches sur votre contenu dans Serveur SharePoint.", - "searchConnectorsPlugin.content.nativeConnectors.sharepointServer.name": "Serveur SharePoint", - "searchConnectorsPlugin.content.nativeConnectors.slack.description": "Effectuez des recherches sur votre contenu dans Slack.", - "searchConnectorsPlugin.content.nativeConnectors.slack.name": "Slack", - "searchConnectorsPlugin.content.nativeConnectors.teams.description": "Effectuez des recherches sur votre contenu dans Teams.", - "searchConnectorsPlugin.content.nativeConnectors.teams.name": "Équipes", - "searchConnectorsPlugin.content.nativeConnectors.zoom.description": "Effectuez des recherches sur votre contenu dans Zoom.", - "searchConnectorsPlugin.content.nativeConnectors.zoom.name": "Effectuer un zoom", "searchErrors.errors.fetchError": "Vérifiez votre connexion réseau et réessayez.", "searchErrors.esError.unknownRootCause": "inconnue", "searchErrors.esError.viewDetailsButtonLabel": "Afficher les détails", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e10155c086e64..64a5214cdd770 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6148,11 +6148,61 @@ "searchConnectors.content.indices.connectorScheduling.schedulePanel.contentSync.description": "Elasticsearchドキュメントを作成または更新するためにコンテンツを取得します。", "searchConnectors.content.indices.connectorScheduling.schedulePanel.contentSync.title": "コンテンツ同期", "searchConnectors.content.indices.connectorScheduling.switch.label": "有効", + "searchConnectors.content.nativeConnectors.azureBlob.description": "Azure Blob Storageのコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.azureBlob.name": "Azure Blob Storage", + "searchConnectors.content.nativeConnectors.box.description": "Boxでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.box.name": "Box", + "searchConnectors.content.nativeConnectors.confluence_data_center.name": "Confluence Data Center", + "searchConnectors.content.nativeConnectors.confluence.description": "Confluence Cloudでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.confluence.name": "Confluence Cloud & Server", + "searchConnectors.content.nativeConnectors.confluenceDataCenter.description": "Confluence Data Centerでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.customConnector.description": "カスタムデータソースに格納されているデータを検索します。", + "searchConnectors.content.nativeConnectors.customConnector.name": "カスタマイズされたコネクター", + "searchConnectors.content.nativeConnectors.dropbox.description": "Dropboxに保存されたファイルとフォルダーを検索します。", + "searchConnectors.content.nativeConnectors.dropbox.name": "Dropbox", + "searchConnectors.content.nativeConnectors.github.description": "GitHubのプロジェクトとリポジトリを検索します。", + "searchConnectors.content.nativeConnectors.github.name": "GitHub & GitHub Enterprise Server", + "searchConnectors.content.nativeConnectors.gmail.description": "Gmailでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.gmail.name": "Gmail", + "searchConnectors.content.nativeConnectors.googleCloud.description": "Google Cloud Storageのコンテンツを検索します。", "searchConnectors.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", + "searchConnectors.content.nativeConnectors.googleDrive.description": "Google Driveのコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.googleDrive.name": "Google Drive", + "searchConnectors.content.nativeConnectors.graphQL.description": "GraphQLでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.graphQL.name": "GraphQL", + "searchConnectors.content.nativeConnectors.jira_data_center.name": "Jira Data Center", + "searchConnectors.content.nativeConnectors.jira.description": "Jira Cloudでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.jira.name": "Jira Cloud", + "searchConnectors.content.nativeConnectors.jiraDataCenter.description": "Jira Data Centerでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.jiraServer.description": "Jira Serverでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.jiraServer.name": "Jira Server", + "searchConnectors.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", + "searchConnectors.content.nativeConnectors.mongoDB.description": "MongoDBコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.mongodb.name": "MongoDB", + "searchConnectors.content.nativeConnectors.msSql.description": "Microsoft SQL Serverでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.mysql.description": "MySQLコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.mysql.name": "MySQL", + "searchConnectors.content.nativeConnectors.netowkrDrive.description": "ネットワークドライブコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.networkDrive.name": "ネットワークドライブ", + "searchConnectors.content.nativeConnectors.notion.description": "Notionでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.notion.name": "Notion", + "searchConnectors.content.nativeConnectors.oneDrive.description": "OneDriveでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.oneDrive.name": "OneDrive", + "searchConnectors.content.nativeConnectors.openTextDocumentum.description": "OpenText Documentumでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.openTextDocumentum.name": "OpenText Documentum", + "searchConnectors.content.nativeConnectors.oracle.description": "Oracleでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.oracle.name": "Oracle", + "searchConnectors.content.nativeConnectors.outlook.description": "Outlookでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.outlook.name": "Outlook", + "searchConnectors.content.nativeConnectors.postgreSQL.description": "PostgreSQLでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.postgresql.name": "PostgreSQL", + "searchConnectors.content.nativeConnectors.redis.description": "Redisでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.redis.name": "Redis", "searchConnectors.content.nativeConnectors.s3.accessKey.label": "AWSアクセスキーID", "searchConnectors.content.nativeConnectors.s3.buckets.label": "AWSバケット", "searchConnectors.content.nativeConnectors.s3.buckets.tooltip": "詳細同期ルールが使用されている場合、AWSバケットは無視されます。", "searchConnectors.content.nativeConnectors.s3.connectTimeout.label": "接続タイムアウト", + "searchConnectors.content.nativeConnectors.s3.description": "Amazon S3でコンテンツを検索します。", "searchConnectors.content.nativeConnectors.s3.maxAttempts.label": "最大再試行回数", "searchConnectors.content.nativeConnectors.s3.maxPageSize.label": "ページの最大サイズ", "searchConnectors.content.nativeConnectors.s3.name": "S3", @@ -6162,9 +6212,24 @@ "searchConnectors.content.nativeConnectors.salesforce.clientId.tooltip": "OAuth2対応接続済みアプリのクライアントID。「コンシューマーキー」とも呼ばれます。", "searchConnectors.content.nativeConnectors.salesforce.clientSecret.label": "クライアントシークレット", "searchConnectors.content.nativeConnectors.salesforce.clientSecret.tooltip": "OAuth2対応接続済みアプリのクライアントシークレット。「コンシューマーシークレット」とも呼ばれます。", + "searchConnectors.content.nativeConnectors.salesforce.description": "Salesforceでコンテンツを検索します。", "searchConnectors.content.nativeConnectors.salesforce.domain.label": "ドメイン", "searchConnectors.content.nativeConnectors.salesforce.domain.tooltip": "Salesforceインスタンスのドメイン。Salesforce URLがhttps://foo.salesforce.comの場合は、ドメインが「foo」になります。", "searchConnectors.content.nativeConnectors.salesforce.name": "Salesforce", + "searchConnectors.content.nativeConnectors.salesforceBox.name": "Salesforce Sandbox", + "searchConnectors.content.nativeConnectors.salesforceSandbox.description": "Salesforce Sandboxでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.serviceNow.description": "ServiceNowでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.serviceNow.name": "ServiceNow", + "searchConnectors.content.nativeConnectors.sharepointOnline.description": "SharePoint Onlineでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.sharepointOnline.name": "Sharepoint Online", + "searchConnectors.content.nativeConnectors.sharepointServer.description": "SharePoint Serverでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.sharepointServer.name": "Sharepoint Server", + "searchConnectors.content.nativeConnectors.slack.description": "Slackでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.slack.name": "Slack", + "searchConnectors.content.nativeConnectors.teams.description": "Teamsでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.teams.name": "Teams", + "searchConnectors.content.nativeConnectors.zoom.description": "Zoomでコンテンツを検索します。", + "searchConnectors.content.nativeConnectors.zoom.name": "ズーム", "searchConnectors.cronEditor.cronDaily.fieldHour.textAtLabel": "に", "searchConnectors.cronEditor.cronDaily.fieldTimeLabel": "時間", "searchConnectors.cronEditor.cronDaily.hourSelectLabel": "時間", @@ -6538,74 +6603,6 @@ "searchConnectors.syncStatus.inProgress": "同期は実行中です", "searchConnectors.syncStatus.pending": "同期は保留中です", "searchConnectors.syncStatus.suspended": "同期が一時停止されました", - "searchConnectorsPlugin.content.nativeConnectors.azureBlob.description": "Azure Blob Storageのコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.azureBlob.name": "Azure Blob Storage", - "searchConnectorsPlugin.content.nativeConnectors.box.description": "Boxでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.box.name": "Box", - "searchConnectorsPlugin.content.nativeConnectors.confluence_data_center.name": "Confluence Data Center", - "searchConnectorsPlugin.content.nativeConnectors.confluence.description": "Confluence Cloudでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.confluence.name": "Confluence Cloud & Server", - "searchConnectorsPlugin.content.nativeConnectors.confluenceDataCenter.description": "Confluence Data Centerでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.customConnector.description": "カスタムデータソースに格納されているデータを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.customConnector.name": "カスタマイズされたコネクター", - "searchConnectorsPlugin.content.nativeConnectors.dropbox.description": "Dropboxに保存されたファイルとフォルダーを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.dropbox.name": "Dropbox", - "searchConnectorsPlugin.content.nativeConnectors.github.description": "GitHubのプロジェクトとリポジトリを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.github.name": "GitHub & GitHub Enterprise Server", - "searchConnectorsPlugin.content.nativeConnectors.gmail.description": "Gmailでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.gmail.name": "Gmail", - "searchConnectorsPlugin.content.nativeConnectors.googleCloud.description": "Google Cloud Storageのコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", - "searchConnectorsPlugin.content.nativeConnectors.googleDrive.description": "Google Driveのコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.googleDrive.name": "Google Drive", - "searchConnectorsPlugin.content.nativeConnectors.graphQL.description": "GraphQLでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.graphQL.name": "GraphQL", - "searchConnectorsPlugin.content.nativeConnectors.jira_data_center.name": "Jira Data Center", - "searchConnectorsPlugin.content.nativeConnectors.jira.description": "Jira Cloudでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.jira.name": "Jira Cloud", - "searchConnectorsPlugin.content.nativeConnectors.jiraDataCenter.description": "Jira Data Centerでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.jiraServer.description": "Jira Serverでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.jiraServer.name": "Jira Server", - "searchConnectorsPlugin.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", - "searchConnectorsPlugin.content.nativeConnectors.mongoDB.description": "MongoDBコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.mongodb.name": "MongoDB", - "searchConnectorsPlugin.content.nativeConnectors.msSql.description": "Microsoft SQL Serverでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.mysql.description": "MySQLコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.mysql.name": "MySQL", - "searchConnectorsPlugin.content.nativeConnectors.netowkrDrive.description": "ネットワークドライブコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.networkDrive.name": "ネットワークドライブ", - "searchConnectorsPlugin.content.nativeConnectors.notion.description": "Notionでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.notion.name": "Notion", - "searchConnectorsPlugin.content.nativeConnectors.oneDrive.description": "OneDriveでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.oneDrive.name": "OneDrive", - "searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.description": "OpenText Documentumでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.name": "OpenText Documentum", - "searchConnectorsPlugin.content.nativeConnectors.oracle.description": "Oracleでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.oracle.name": "Oracle", - "searchConnectorsPlugin.content.nativeConnectors.outlook.description": "Outlookでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.outlook.name": "Outlook", - "searchConnectorsPlugin.content.nativeConnectors.postgreSQL.description": "PostgreSQLでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.postgresql.name": "PostgreSQL", - "searchConnectorsPlugin.content.nativeConnectors.redis.description": "Redisでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.redis.name": "Redis", - "searchConnectorsPlugin.content.nativeConnectors.s3.description": "Amazon S3でコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.s3.name": "S3", - "searchConnectorsPlugin.content.nativeConnectors.salesforce.description": "Salesforceでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.salesforce.name": "Salesforce", - "searchConnectorsPlugin.content.nativeConnectors.salesforceBox.name": "Salesforce Sandbox", - "searchConnectorsPlugin.content.nativeConnectors.salesforceSandbox.description": "Salesforce Sandboxでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.serviceNow.description": "ServiceNowでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.serviceNow.name": "ServiceNow", - "searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.description": "SharePoint Onlineでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.name": "Sharepoint Online", - "searchConnectorsPlugin.content.nativeConnectors.sharepointServer.description": "SharePoint Serverでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.sharepointServer.name": "Sharepoint Server", - "searchConnectorsPlugin.content.nativeConnectors.slack.description": "Slackでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.slack.name": "Slack", - "searchConnectorsPlugin.content.nativeConnectors.teams.description": "Teamsでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.teams.name": "Teams", - "searchConnectorsPlugin.content.nativeConnectors.zoom.description": "Zoomでコンテンツを検索します。", - "searchConnectorsPlugin.content.nativeConnectors.zoom.name": "ズーム", "searchErrors.errors.fetchError": "ネットワーク接続を確認して再試行してください。", "searchErrors.esError.unknownRootCause": "不明", "searchErrors.esError.viewDetailsButtonLabel": "詳細を表示", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0ed330ec41630..f42192834b229 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6161,11 +6161,61 @@ "searchConnectors.content.indices.connectorScheduling.schedulePanel.contentSync.description": "提取内容以创建或更新您的 Elasticsearch 文档。", "searchConnectors.content.indices.connectorScheduling.schedulePanel.contentSync.title": "内容同步", "searchConnectors.content.indices.connectorScheduling.switch.label": "已启用", + "searchConnectors.content.nativeConnectors.azureBlob.description": "在 Azure Blob 存储上搜索您的内容。", + "searchConnectors.content.nativeConnectors.azureBlob.name": "Azure Blob 存储", + "searchConnectors.content.nativeConnectors.box.description": "在 Box 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.box.name": "Box", + "searchConnectors.content.nativeConnectors.confluence_data_center.name": "Confluence 数据中心", + "searchConnectors.content.nativeConnectors.confluence.description": "在 Confluence Cloud 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.confluence.name": "Confluence Cloud 和 Confluence Server", + "searchConnectors.content.nativeConnectors.confluenceDataCenter.description": "在 Confluence 数据中心上搜索您的内容。", + "searchConnectors.content.nativeConnectors.customConnector.description": "搜索存储在定制数据源上的数据。", + "searchConnectors.content.nativeConnectors.customConnector.name": "定制连接器", + "searchConnectors.content.nativeConnectors.dropbox.description": "搜索存储在 Dropbox 上的文件和文件夹。", + "searchConnectors.content.nativeConnectors.dropbox.name": "Dropbox", + "searchConnectors.content.nativeConnectors.github.description": "搜索 GitHub 上的项目和存储库。", + "searchConnectors.content.nativeConnectors.github.name": "GitHub 和 GitHub Enterprise Server", + "searchConnectors.content.nativeConnectors.gmail.description": "在 Gmail 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.gmail.name": "Gmail", + "searchConnectors.content.nativeConnectors.googleCloud.description": "在 Google Cloud Storage 上搜索您的内容。", "searchConnectors.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", + "searchConnectors.content.nativeConnectors.googleDrive.description": "在 Google 云端硬盘上搜索您的内容。", + "searchConnectors.content.nativeConnectors.googleDrive.name": "Google 云端硬盘", + "searchConnectors.content.nativeConnectors.graphQL.description": "使用 GraphQL 搜索您的内容。", + "searchConnectors.content.nativeConnectors.graphQL.name": "GraphQL", + "searchConnectors.content.nativeConnectors.jira_data_center.name": "Jira 数据中心", + "searchConnectors.content.nativeConnectors.jira.description": "在 Jira Cloud 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.jira.name": "Jira Cloud", + "searchConnectors.content.nativeConnectors.jiraDataCenter.description": "在 Jira 数据中心上搜索您的内容。", + "searchConnectors.content.nativeConnectors.jiraServer.description": "在 Jira Server 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.jiraServer.name": "Jira Server", + "searchConnectors.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", + "searchConnectors.content.nativeConnectors.mongoDB.description": "搜索您的 MongoDB 内容。", + "searchConnectors.content.nativeConnectors.mongodb.name": "MongoDB", + "searchConnectors.content.nativeConnectors.msSql.description": "在 Microsoft SQL Server 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.mysql.description": "搜索您的 MySQL 内容。", + "searchConnectors.content.nativeConnectors.mysql.name": "MySQL", + "searchConnectors.content.nativeConnectors.netowkrDrive.description": "搜索您的网络驱动器内容。", + "searchConnectors.content.nativeConnectors.networkDrive.name": "网络驱动器", + "searchConnectors.content.nativeConnectors.notion.description": "在 Notion 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.notion.name": "Notion", + "searchConnectors.content.nativeConnectors.oneDrive.description": "在 OneDrive 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.oneDrive.name": "OneDrive", + "searchConnectors.content.nativeConnectors.openTextDocumentum.description": "在 OpenText Documentum 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.openTextDocumentum.name": "OpenText Documentum", + "searchConnectors.content.nativeConnectors.oracle.description": "在 Oracle 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.oracle.name": "Oracle", + "searchConnectors.content.nativeConnectors.outlook.description": "在 Outlook 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.outlook.name": "Outlook", + "searchConnectors.content.nativeConnectors.postgreSQL.description": "在 PostgreSQL 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.postgresql.name": "PostgreSQL", + "searchConnectors.content.nativeConnectors.redis.description": "在 Redis 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.redis.name": "Redis", "searchConnectors.content.nativeConnectors.s3.accessKey.label": "AWS 访问密钥 ID", "searchConnectors.content.nativeConnectors.s3.buckets.label": "AWS 存储桶", "searchConnectors.content.nativeConnectors.s3.buckets.tooltip": "使用高级同步规则时,将忽略 AWS 存储桶。", "searchConnectors.content.nativeConnectors.s3.connectTimeout.label": "连接超时", + "searchConnectors.content.nativeConnectors.s3.description": "在 Amazon S3 上搜索您的内容。", "searchConnectors.content.nativeConnectors.s3.maxAttempts.label": "最大重试次数", "searchConnectors.content.nativeConnectors.s3.maxPageSize.label": "最大页面大小", "searchConnectors.content.nativeConnectors.s3.name": "S3", @@ -6175,9 +6225,24 @@ "searchConnectors.content.nativeConnectors.salesforce.clientId.tooltip": "启用了 OAuth2 的已连接应用的客户端 ID。也称为“使用者密钥”", "searchConnectors.content.nativeConnectors.salesforce.clientSecret.label": "客户端密钥", "searchConnectors.content.nativeConnectors.salesforce.clientSecret.tooltip": "启用了 OAuth2 的已连接应用的客户端密钥。也称为“使用者机密”", + "searchConnectors.content.nativeConnectors.salesforce.description": "在 Salesforce 上搜索您的内容。", "searchConnectors.content.nativeConnectors.salesforce.domain.label": "域", "searchConnectors.content.nativeConnectors.salesforce.domain.tooltip": "Salesforce 实例的域。如果 Salesforce URL 为“https://foo.salesforce.com”,则该域将为“foo”。", "searchConnectors.content.nativeConnectors.salesforce.name": "Salesforce", + "searchConnectors.content.nativeConnectors.salesforceBox.name": "Salesforce Sandbox", + "searchConnectors.content.nativeConnectors.salesforceSandbox.description": "在 Salesforce Sandbox 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.serviceNow.description": "在 ServiceNow 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.serviceNow.name": "ServiceNow", + "searchConnectors.content.nativeConnectors.sharepointOnline.description": "在 SharePoint Online 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.sharepointOnline.name": "Sharepoint", + "searchConnectors.content.nativeConnectors.sharepointServer.description": "在 SharePoint Server 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.sharepointServer.name": "SharePoint Server", + "searchConnectors.content.nativeConnectors.slack.description": "在 Slack 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.slack.name": "Slack", + "searchConnectors.content.nativeConnectors.teams.description": "在 Teams 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.teams.name": "Teams", + "searchConnectors.content.nativeConnectors.zoom.description": "在 Zoom 上搜索您的内容。", + "searchConnectors.content.nativeConnectors.zoom.name": "缩放", "searchConnectors.cronEditor.cronDaily.fieldHour.textAtLabel": "于", "searchConnectors.cronEditor.cronDaily.fieldTimeLabel": "时间", "searchConnectors.cronEditor.cronDaily.hourSelectLabel": "小时", @@ -6551,74 +6616,6 @@ "searchConnectors.syncStatus.inProgress": "同步进行中", "searchConnectors.syncStatus.pending": "同步待处理", "searchConnectors.syncStatus.suspended": "同步已挂起", - "searchConnectorsPlugin.content.nativeConnectors.azureBlob.description": "在 Azure Blob 存储上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.azureBlob.name": "Azure Blob 存储", - "searchConnectorsPlugin.content.nativeConnectors.box.description": "在 Box 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.box.name": "Box", - "searchConnectorsPlugin.content.nativeConnectors.confluence_data_center.name": "Confluence 数据中心", - "searchConnectorsPlugin.content.nativeConnectors.confluence.description": "在 Confluence Cloud 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.confluence.name": "Confluence Cloud 和 Confluence Server", - "searchConnectorsPlugin.content.nativeConnectors.confluenceDataCenter.description": "在 Confluence 数据中心上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.customConnector.description": "搜索存储在定制数据源上的数据。", - "searchConnectorsPlugin.content.nativeConnectors.customConnector.name": "定制连接器", - "searchConnectorsPlugin.content.nativeConnectors.dropbox.description": "搜索存储在 Dropbox 上的文件和文件夹。", - "searchConnectorsPlugin.content.nativeConnectors.dropbox.name": "Dropbox", - "searchConnectorsPlugin.content.nativeConnectors.github.description": "搜索 GitHub 上的项目和存储库。", - "searchConnectorsPlugin.content.nativeConnectors.github.name": "GitHub 和 GitHub Enterprise Server", - "searchConnectorsPlugin.content.nativeConnectors.gmail.description": "在 Gmail 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.gmail.name": "Gmail", - "searchConnectorsPlugin.content.nativeConnectors.googleCloud.description": "在 Google Cloud Storage 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.googleCloud.name": "Google Cloud Storage", - "searchConnectorsPlugin.content.nativeConnectors.googleDrive.description": "在 Google 云端硬盘上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.googleDrive.name": "Google 云端硬盘", - "searchConnectorsPlugin.content.nativeConnectors.graphQL.description": "使用 GraphQL 搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.graphQL.name": "GraphQL", - "searchConnectorsPlugin.content.nativeConnectors.jira_data_center.name": "Jira 数据中心", - "searchConnectorsPlugin.content.nativeConnectors.jira.description": "在 Jira Cloud 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.jira.name": "Jira Cloud", - "searchConnectorsPlugin.content.nativeConnectors.jiraDataCenter.description": "在 Jira 数据中心上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.jiraServer.description": "在 Jira Server 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.jiraServer.name": "Jira Server", - "searchConnectorsPlugin.content.nativeConnectors.microsoftSQL.name": "Microsoft SQL", - "searchConnectorsPlugin.content.nativeConnectors.mongoDB.description": "搜索您的 MongoDB 内容。", - "searchConnectorsPlugin.content.nativeConnectors.mongodb.name": "MongoDB", - "searchConnectorsPlugin.content.nativeConnectors.msSql.description": "在 Microsoft SQL Server 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.mysql.description": "搜索您的 MySQL 内容。", - "searchConnectorsPlugin.content.nativeConnectors.mysql.name": "MySQL", - "searchConnectorsPlugin.content.nativeConnectors.netowkrDrive.description": "搜索您的网络驱动器内容。", - "searchConnectorsPlugin.content.nativeConnectors.networkDrive.name": "网络驱动器", - "searchConnectorsPlugin.content.nativeConnectors.notion.description": "在 Notion 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.notion.name": "Notion", - "searchConnectorsPlugin.content.nativeConnectors.oneDrive.description": "在 OneDrive 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.oneDrive.name": "OneDrive", - "searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.description": "在 OpenText Documentum 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.openTextDocumentum.name": "OpenText Documentum", - "searchConnectorsPlugin.content.nativeConnectors.oracle.description": "在 Oracle 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.oracle.name": "Oracle", - "searchConnectorsPlugin.content.nativeConnectors.outlook.description": "在 Outlook 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.outlook.name": "Outlook", - "searchConnectorsPlugin.content.nativeConnectors.postgreSQL.description": "在 PostgreSQL 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.postgresql.name": "PostgreSQL", - "searchConnectorsPlugin.content.nativeConnectors.redis.description": "在 Redis 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.redis.name": "Redis", - "searchConnectorsPlugin.content.nativeConnectors.s3.description": "在 Amazon S3 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.s3.name": "S3", - "searchConnectorsPlugin.content.nativeConnectors.salesforce.description": "在 Salesforce 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.salesforce.name": "Salesforce", - "searchConnectorsPlugin.content.nativeConnectors.salesforceBox.name": "Salesforce Sandbox", - "searchConnectorsPlugin.content.nativeConnectors.salesforceSandbox.description": "在 Salesforce Sandbox 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.serviceNow.description": "在 ServiceNow 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.serviceNow.name": "ServiceNow", - "searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.description": "在 SharePoint Online 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.sharepointOnline.name": "Sharepoint", - "searchConnectorsPlugin.content.nativeConnectors.sharepointServer.description": "在 SharePoint Server 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.sharepointServer.name": "SharePoint Server", - "searchConnectorsPlugin.content.nativeConnectors.slack.description": "在 Slack 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.slack.name": "Slack", - "searchConnectorsPlugin.content.nativeConnectors.teams.description": "在 Teams 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.teams.name": "Teams", - "searchConnectorsPlugin.content.nativeConnectors.zoom.description": "在 Zoom 上搜索您的内容。", - "searchConnectorsPlugin.content.nativeConnectors.zoom.name": "缩放", "searchErrors.errors.fetchError": "检查您的网络连接,然后重试。", "searchErrors.esError.unknownRootCause": "未知", "searchErrors.esError.viewDetailsButtonLabel": "查看详情", From b4bdd125621f5381669230108f64174e75dc40da Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 6 Nov 2024 09:45:32 -0600 Subject: [PATCH 20/53] skip failing test suites (#185046, #185879) --- .../components/case_view/components/custom_fields.test.tsx | 3 ++- .../plugins/cases/public/components/description/index.test.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx index 67d8f8fd05764..31d4395fc3d7a 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/custom_fields.test.tsx @@ -16,7 +16,8 @@ import { customFieldsMock, customFieldsConfigurationMock } from '../../../contai import userEvent from '@testing-library/user-event'; import { CustomFieldTypes } from '../../../../common/types/domain'; -describe('Case View Page files tab', () => { +// Failing: See https://github.com/elastic/kibana/issues/185046 +describe.skip('Case View Page files tab', () => { const onSubmit = jest.fn(); let appMockRender: AppMockRenderer; diff --git a/x-pack/plugins/cases/public/components/description/index.test.tsx b/x-pack/plugins/cases/public/components/description/index.test.tsx index 5ce8909a65dc6..678b46eabfbe8 100644 --- a/x-pack/plugins/cases/public/components/description/index.test.tsx +++ b/x-pack/plugins/cases/public/components/description/index.test.tsx @@ -27,7 +27,8 @@ const defaultProps = { isLoadingDescription: false, }; -describe('Description', () => { +// Failing: See https://github.com/elastic/kibana/issues/185879 +describe.skip('Description', () => { const onUpdateField = jest.fn(); let appMockRender: AppMockRenderer; From be3c159cfc9915abc48a409e123ee11070d34dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 6 Nov 2024 17:09:41 +0100 Subject: [PATCH 21/53] [Rendering] Parallelize ES requests (#199124) --- .../src/rendering_service.tsx | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx index 44841ec0fbe3f..ace0399f242af 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx +++ b/packages/core/rendering/core-rendering-server-internal/src/rendering_service.tsx @@ -20,10 +20,10 @@ import type { IUiSettingsClient } from '@kbn/core-ui-settings-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import type { CustomBranding } from '@kbn/core-custom-branding-common'; import { - type UserProvidedValues, type DarkModeValue, parseDarkModeValue, type UiSettingsParams, + type UserProvidedValues, } from '@kbn/core-ui-settings-common'; import { Template } from './views'; import { @@ -148,23 +148,29 @@ export class RenderingService { const basePath = http.basePath.get(request); const { serverBasePath, publicBaseUrl } = http.basePath; - let settingsUserValues: Record = {}; - let globalSettingsUserValues: Record = {}; - - if (!isAnonymousPage) { - const userValues = await Promise.all([ - uiSettings.client?.getUserProvided(), - uiSettings.globalClient?.getUserProvided(), - ]); - - settingsUserValues = userValues[0]; - globalSettingsUserValues = userValues[1]; - } - - const defaultSettings = await withAsyncDefaultValues( - request, - uiSettings.client?.getRegistered() - ); + // Grouping all async HTTP requests to run them concurrently for performance reasons. + const [ + defaultSettings, + settingsUserValues = {}, + globalSettingsUserValues = {}, + userSettingDarkMode, + ] = await Promise.all([ + // All sites + withAsyncDefaultValues(request, uiSettings.client?.getRegistered()), + // Only non-anonymous pages + ...(!isAnonymousPage + ? ([ + uiSettings.client?.getUserProvided(), + uiSettings.globalClient?.getUserProvided(), + // dark mode + userSettings?.getUserSettingDarkMode(request), + ] as [ + Promise>, + Promise>, + Promise | undefined + ]) + : []), + ]); const settings = { defaults: defaultSettings, @@ -196,10 +202,6 @@ export class RenderingService { } // dark mode - const userSettingDarkMode = isAnonymousPage - ? undefined - : await userSettings?.getUserSettingDarkMode(request); - const isThemeOverridden = settings.user['theme:darkMode']?.isOverridden ?? false; let darkMode: DarkModeValue; From 8054aa253fddc5be6c88904024518d30f5d8ae1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 6 Nov 2024 17:10:06 +0100 Subject: [PATCH 22/53] Flaky #111821 - Refresh index (#199136) --- .../rollups/integration_tests/daily_rollups.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts index c29f8041d8044..3433af684e92c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts @@ -8,7 +8,12 @@ */ import moment, { type MomentInput } from 'moment'; -import type { Logger, ISavedObjectsRepository, SavedObject } from '@kbn/core/server'; +import type { + Logger, + ISavedObjectsRepository, + SavedObject, + ElasticsearchClient, +} from '@kbn/core/server'; import { type TestElasticsearchUtils, type TestKibanaUtils, @@ -72,11 +77,11 @@ function createRawEventLoopDelaysDailyDocs() { return { rawEventLoopDelaysDaily, outdatedRawEventLoopDelaysDaily }; } -// Failing: See https://github.com/elastic/kibana/issues/111821 -describe.skip(`daily rollups integration test`, () => { +describe(`daily rollups integration test`, () => { let esServer: TestElasticsearchUtils; let root: TestKibanaUtils['root']; let internalRepository: ISavedObjectsRepository; + let esClient: ElasticsearchClient; let logger: Logger; let rawEventLoopDelaysDaily: Array>; let outdatedRawEventLoopDelaysDaily: Array>; @@ -94,6 +99,7 @@ describe.skip(`daily rollups integration test`, () => { const start = await root.start(); logger = root.logger.get('test daily rollups'); internalRepository = start.savedObjects.createInternalRepository([SAVED_OBJECTS_DAILY_TYPE]); + esClient = start.elasticsearch.client.asInternalUser; // Create the docs now const rawDailyDocs = createRawEventLoopDelaysDailyDocs(); @@ -113,6 +119,7 @@ describe.skip(`daily rollups integration test`, () => { it('deletes documents older that 3 days from the saved objects repository', async () => { await rollDailyData(logger, internalRepository); + await esClient.indices.refresh({ index: `.kibana` }); // Make sure that the changes are searchable const { total, saved_objects: savedObjects } = await internalRepository.find({ type: SAVED_OBJECTS_DAILY_TYPE }); expect(total).toBe(rawEventLoopDelaysDaily.length); From 3e0ec510fab85d3b7346eb88b8a3bed0dc5b73f5 Mon Sep 17 00:00:00 2001 From: Yngrid Coello Date: Wed, 6 Nov 2024 17:29:09 +0100 Subject: [PATCH 23/53] [Dataset quality] Extracting totalDocs form degradedDocs request (#198757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relates to https://github.com/elastic/logs-dev/issues/183 ## Summary This PR aims to split out `total_docs` from `degraded_docs` request. This number is no longer relevant only for degraded docs. This PR is a preparation step for supporting `failed_docs`. ### 🎥 Demo https://github.com/user-attachments/assets/7a826715-64e2-4799-8b54-934698df56e2 #### When no documents are found in the selected timerange https://github.com/user-attachments/assets/de974125-cf45-42d3-932f-32e43b282eb2 #### Filtering datasets https://github.com/user-attachments/assets/398fc7db-1e38-4998-9ecb-10e8644f812d ### TODO - [ ] Test in MKI before merging --- .../dataset_quality/common/api_types.ts | 34 +- .../dataset_quality/common/constants.ts | 5 +- .../data_streams_stats/data_stream_stat.ts | 34 +- .../data_streams_stats/malformed_docs_stat.ts | 31 -- .../common/data_streams_stats/types.ts | 12 +- .../dataset_quality_indicator.tsx | 4 +- .../hooks/use_dataset_quality_filters.ts | 2 +- .../hooks/use_dataset_quality_table.tsx | 3 +- .../public/hooks/use_dataset_telemetry.ts | 2 +- .../public/hooks/use_summary_panel.ts | 2 +- .../data_streams_stats_client.ts | 46 ++- .../services/data_streams_stats/types.ts | 7 +- .../src/defaults.ts | 3 +- .../src/notifications.ts | 13 + .../src/state_machine.ts | 106 ++++-- .../dataset_quality_controller/src/types.ts | 17 +- .../public/utils/generate_datasets.test.ts | 335 +++++++++++++----- .../public/utils/generate_datasets.ts | 58 +-- ...et_dataset_aggregated_paginated_results.ts | 94 +++++ .../routes/data_streams/get_degraded_docs.ts | 168 ++------- .../server/routes/data_streams/routes.ts | 47 ++- .../dataset_quality/data_stream_total_docs.ts | 132 +++++++ .../observability/dataset_quality/index.ts | 1 + .../tests/data_streams/degraded_docs.spec.ts | 96 +---- .../tests/data_streams/total_docs.spec.ts | 41 +++ 25 files changed, 809 insertions(+), 484 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/malformed_docs_stat.ts create mode 100644 x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_dataset_aggregated_paginated_results.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/data_stream_total_docs.ts create mode 100644 x-pack/test/dataset_quality_api_integration/tests/data_streams/total_docs.spec.ts diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts index 903d7f0607663..51a1421aec918 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts @@ -37,6 +37,25 @@ export const dataStreamStatRt = rt.intersection([ export type DataStreamStat = rt.TypeOf; +export const dataStreamDocsStatRt = rt.type({ + dataset: rt.string, + count: rt.number, +}); + +export type DataStreamDocsStat = rt.TypeOf; + +export const getDataStreamTotalDocsResponseRt = rt.type({ + totalDocs: rt.array(dataStreamDocsStatRt), +}); + +export type DataStreamTotalDocsResponse = rt.TypeOf; + +export const getDataStreamDegradedDocsResponseRt = rt.type({ + degradedDocs: rt.array(dataStreamDocsStatRt), +}); + +export type DataStreamDegradedDocsResponse = rt.TypeOf; + export const integrationDashboardRT = rt.type({ id: rt.string, title: rt.string, @@ -84,15 +103,6 @@ export const getIntegrationsResponseRt = rt.exact( export type IntegrationResponse = rt.TypeOf; -export const degradedDocsRt = rt.type({ - dataset: rt.string, - count: rt.number, - docsCount: rt.number, - percentage: rt.number, -}); - -export type DegradedDocs = rt.TypeOf; - export const degradedFieldRt = rt.type({ name: rt.string, count: rt.number, @@ -188,12 +198,6 @@ export const getDataStreamsStatsResponseRt = rt.exact( }) ); -export const getDataStreamsDegradedDocsStatsResponseRt = rt.exact( - rt.type({ - degradedDocs: rt.array(degradedDocsRt), - }) -); - export const getDataStreamsSettingsResponseRt = rt.exact(dataStreamSettingsRt); export const getDataStreamsDetailsResponseRt = rt.exact(dataStreamDetailsRt); diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/constants.ts b/x-pack/plugins/observability_solution/dataset_quality/common/constants.ts index 1b822c6c111d9..74809e0e19420 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/constants.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/constants.ts @@ -11,6 +11,7 @@ export const DATASET_QUALITY_APP_ID = 'dataset_quality'; export const DEFAULT_DATASET_TYPE: DataStreamType = 'logs'; export const DEFAULT_LOGS_DATA_VIEW = 'logs-*-*'; +export const DEFAULT_DATASET_QUALITY: QualityIndicators = 'good'; export const POOR_QUALITY_MINIMUM_PERCENTAGE = 3; export const DEGRADED_QUALITY_MINIMUM_PERCENTAGE = 0; @@ -26,10 +27,8 @@ export const DEFAULT_TIME_RANGE = { from: 'now-24h', to: 'now' }; export const DEFAULT_DATEPICKER_REFRESH = { value: 60000, pause: false }; export const DEFAULT_DEGRADED_DOCS = { - percentage: 0, count: 0, - docsCount: 0, - quality: 'good' as QualityIndicators, + percentage: 0, }; export const NUMBER_FORMAT = '0,0.[000]'; diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts index 164a43c625fb1..094d92ff3fea6 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { DEFAULT_DEGRADED_DOCS } from '../constants'; +import { DataStreamDocsStat } from '../api_types'; +import { DEFAULT_DATASET_QUALITY, DEFAULT_DEGRADED_DOCS } from '../constants'; import { DataStreamType, QualityIndicators } from '../types'; import { indexNameToDataStreamParts, mapPercentageToQuality } from '../utils'; import { Integration } from './integration'; -import { DegradedDocsStat } from './malformed_docs_stat'; import { DataStreamStatType } from './types'; export class DataStreamStat { @@ -24,11 +24,11 @@ export class DataStreamStat { userPrivileges?: DataStreamStatType['userPrivileges']; totalDocs?: DataStreamStatType['totalDocs']; // total datastream docs count integration?: Integration; + quality: QualityIndicators; + docsInTimeRange?: number; degradedDocs: { percentage: number; count: number; - docsCount: number; // docs count in the filtered time range - quality: QualityIndicators; }; private constructor(dataStreamStat: DataStreamStat) { @@ -43,12 +43,9 @@ export class DataStreamStat { this.userPrivileges = dataStreamStat.userPrivileges; this.totalDocs = dataStreamStat.totalDocs; this.integration = dataStreamStat.integration; - this.degradedDocs = { - percentage: dataStreamStat.degradedDocs.percentage, - count: dataStreamStat.degradedDocs.count, - docsCount: dataStreamStat.degradedDocs.docsCount, - quality: dataStreamStat.degradedDocs.quality, - }; + this.quality = dataStreamStat.quality; + this.docsInTimeRange = dataStreamStat.docsInTimeRange; + this.degradedDocs = dataStreamStat.degradedDocs; } public static create(dataStreamStat: DataStreamStatType) { @@ -65,6 +62,7 @@ export class DataStreamStat { lastActivity: dataStreamStat.lastActivity, userPrivileges: dataStreamStat.userPrivileges, totalDocs: dataStreamStat.totalDocs, + quality: DEFAULT_DATASET_QUALITY, degradedDocs: DEFAULT_DEGRADED_DOCS, }; @@ -74,9 +72,11 @@ export class DataStreamStat { public static fromDegradedDocStat({ degradedDocStat, datasetIntegrationMap, + totalDocs, }: { - degradedDocStat: DegradedDocsStat; + degradedDocStat: DataStreamDocsStat & { percentage: number }; datasetIntegrationMap: Record; + totalDocs: number; }) { const { type, dataset, namespace } = indexNameToDataStreamParts(degradedDocStat.dataset); @@ -87,19 +87,23 @@ export class DataStreamStat { title: datasetIntegrationMap[dataset]?.title || dataset, namespace, integration: datasetIntegrationMap[dataset]?.integration, + quality: mapPercentageToQuality(degradedDocStat.percentage), + docsInTimeRange: totalDocs, degradedDocs: { percentage: degradedDocStat.percentage, count: degradedDocStat.count, - docsCount: degradedDocStat.docsCount, - quality: mapPercentageToQuality(degradedDocStat.percentage), }, }; return new DataStreamStat(dataStreamStatProps); } - public static calculateFilteredSize({ sizeBytes, totalDocs, degradedDocs }: DataStreamStat) { + public static calculateFilteredSize({ sizeBytes, totalDocs, docsInTimeRange }: DataStreamStat) { const avgDocSize = sizeBytes && totalDocs ? sizeBytes / totalDocs : 0; - return avgDocSize * degradedDocs.docsCount; + return avgDocSize * (docsInTimeRange ?? 0); + } + + public static calculatePercentage({ totalDocs, count }: { totalDocs?: number; count?: number }) { + return totalDocs && count ? (count / totalDocs) * 100 : 0; } } diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/malformed_docs_stat.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/malformed_docs_stat.ts deleted file mode 100644 index c86b802ea42da..0000000000000 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/malformed_docs_stat.ts +++ /dev/null @@ -1,31 +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 { QualityIndicators } from '../types'; -import { mapPercentageToQuality } from '../utils'; -import { DegradedDocsStatType } from './types'; - -export class DegradedDocsStat { - dataset: DegradedDocsStatType['dataset']; - percentage: DegradedDocsStatType['percentage']; - count: DegradedDocsStatType['count']; - docsCount: DegradedDocsStatType['docsCount']; - quality: QualityIndicators; - - private constructor(degradedDocsStat: DegradedDocsStat) { - this.dataset = degradedDocsStat.dataset; - this.percentage = degradedDocsStat.percentage; - this.count = degradedDocsStat.count; - this.docsCount = degradedDocsStat.docsCount; - this.quality = degradedDocsStat.quality; - } - - public static create(degradedDocsStat: DegradedDocsStatType) { - const quality = mapPercentageToQuality(degradedDocsStat.percentage); - return new DegradedDocsStat({ ...degradedDocsStat, quality }); - } -} diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts index 1e5adedc20f3a..bc0c12d234d26 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts @@ -18,10 +18,14 @@ export type DataStreamStatServiceResponse = GetDataStreamsStatsResponse; export type GetDataStreamsDegradedDocsStatsParams = APIClientRequestParamsOf<`GET /internal/dataset_quality/data_streams/degraded_docs`>['params']; export type GetDataStreamsDegradedDocsStatsQuery = GetDataStreamsDegradedDocsStatsParams['query']; -export type GetDataStreamsDegradedDocsStatsResponse = - APIReturnType<`GET /internal/dataset_quality/data_streams/degraded_docs`>; -export type DegradedDocsStatType = GetDataStreamsDegradedDocsStatsResponse['degradedDocs'][0]; -export type DataStreamDegradedDocsStatServiceResponse = DegradedDocsStatType[]; + +/* +Types for stats based in documents inside a DataStream +*/ + +export type GetDataStreamsTotalDocsParams = + APIClientRequestParamsOf<`GET /internal/dataset_quality/data_streams/total_docs`>['params']; +export type GetDataStreamsTotalDocsQuery = GetDataStreamsTotalDocsParams['query']; /* Types for Degraded Fields inside a DataStream diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/quality_indicator/dataset_quality_indicator.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/quality_indicator/dataset_quality_indicator.tsx index 419a13272dbc8..78c6d3bff9331 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/quality_indicator/dataset_quality_indicator.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/quality_indicator/dataset_quality_indicator.tsx @@ -19,9 +19,7 @@ export const DatasetQualityIndicator = ({ isLoading: boolean; dataStreamStat: DataStreamStat; }) => { - const { - degradedDocs: { quality }, - } = dataStreamStat; + const { quality } = dataStreamStat; const translatedQuality = i18n.translate('xpack.datasetQuality.datasetQualityIdicator', { defaultMessage: '{quality}', diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_filters.ts b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_filters.ts index e370e7c22d469..056bba2304144 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_filters.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_filters.ts @@ -49,7 +49,7 @@ export const useDatasetQualityFilters = () => { datasets.reduce( (acc: Filters, dataset) => ({ namespaces: [...new Set([...acc.namespaces, dataset.namespace])], - qualities: [...new Set([...acc.qualities, dataset.degradedDocs.quality])], + qualities: [...new Set([...acc.qualities, dataset.quality])], filteredIntegrations: [ ...new Set([...acc.filteredIntegrations, dataset.integration?.name ?? 'none']), ], diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx index 55265a250bb75..6529ae1841ee3 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx @@ -132,8 +132,7 @@ export const useDatasetQualityTable = () => { const passesNamespaceFilter = namespaces.length === 0 || namespaces.includes(dataset.namespace); - const passesQualityFilter = - qualities.length === 0 || qualities.includes(dataset.degradedDocs.quality); + const passesQualityFilter = qualities.length === 0 || qualities.includes(dataset.quality); const passesQueryFilter = !query || dataset.rawName.includes(query); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_telemetry.ts b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_telemetry.ts index 167ebd37fe81a..7d486f94f2607 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_telemetry.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_telemetry.ts @@ -77,7 +77,7 @@ function getDatasetEbtProps( namespace: dataset.namespace, type: dataset.type, }, - data_stream_health: dataset.degradedDocs.quality, + data_stream_health: dataset.quality, data_stream_aggregatable: nonAggregatableDatasets.some( (indexName) => indexName === dataset.rawName ), diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.ts b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.ts index a85dc9c21d222..014d9f578eb60 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.ts @@ -84,7 +84,7 @@ const useSummaryPanel = () => { datasetsActivity, numberOfDatasets: filteredItems.length, - numberOfDocuments: filteredItems.reduce((acc, curr) => acc + curr.degradedDocs.docsCount, 0), + numberOfDocuments: filteredItems.reduce((acc, curr) => acc + curr.docsInTimeRange!, 0), }; }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts index 8642a863726df..8e218819315b2 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts @@ -10,8 +10,11 @@ import { decodeOrThrow } from '@kbn/io-ts-utils'; import rison from '@kbn/rison'; import { KNOWN_TYPES } from '../../../common/constants'; import { - getDataStreamsDegradedDocsStatsResponseRt, + DataStreamDegradedDocsResponse, + DataStreamTotalDocsResponse, + getDataStreamDegradedDocsResponseRt, getDataStreamsStatsResponseRt, + getDataStreamTotalDocsResponseRt, getIntegrationsResponseRt, getNonAggregatableDatasetsRt, IntegrationResponse, @@ -20,9 +23,9 @@ import { import { DataStreamStatServiceResponse, GetDataStreamsDegradedDocsStatsQuery, - GetDataStreamsDegradedDocsStatsResponse, GetDataStreamsStatsQuery, GetDataStreamsStatsResponse, + GetDataStreamsTotalDocsQuery, GetNonAggregatableDataStreamsParams, } from '../../../common/data_streams_stats'; import { Integration } from '../../../common/data_streams_stats/integration'; @@ -56,16 +59,37 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { return { dataStreamsStats, datasetUserPrivileges }; } + public async getDataStreamsTotalDocs(params: GetDataStreamsTotalDocsQuery) { + const response = await this.http + .get('/internal/dataset_quality/data_streams/total_docs', { + query: { + ...params, + }, + }) + .catch((error) => { + throw new DatasetQualityError(`Failed to fetch data streams total docs: ${error}`, error); + }); + + const { totalDocs } = decodeOrThrow( + getDataStreamTotalDocsResponseRt, + (message: string) => + new DatasetQualityError( + `Failed to decode data streams total docs stats response: ${message}` + ) + )(response); + + return totalDocs; + } + public async getDataStreamsDegradedStats(params: GetDataStreamsDegradedDocsStatsQuery) { + const types = params.types.length === 0 ? KNOWN_TYPES : params.types; const response = await this.http - .get( - '/internal/dataset_quality/data_streams/degraded_docs', - { - query: { - ...params, - }, - } - ) + .get('/internal/dataset_quality/data_streams/degraded_docs', { + query: { + ...params, + types: rison.encodeArray(types), + }, + }) .catch((error) => { throw new DatasetQualityError( `Failed to fetch data streams degraded stats: ${error}`, @@ -74,7 +98,7 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { }); const { degradedDocs } = decodeOrThrow( - getDataStreamsDegradedDocsStatsResponseRt, + getDataStreamDegradedDocsResponseRt, (message: string) => new DatasetQualityError( `Failed to decode data streams degraded docs stats response: ${message}` diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts index dd057ee7f3062..240e5519cfc3d 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/types.ts @@ -7,14 +7,14 @@ import { HttpStart } from '@kbn/core/public'; import { - DataStreamDegradedDocsStatServiceResponse, DataStreamStatServiceResponse, GetDataStreamsDegradedDocsStatsQuery, GetDataStreamsStatsQuery, + GetDataStreamsTotalDocsQuery, GetNonAggregatableDataStreamsParams, } from '../../../common/data_streams_stats'; import { Integration } from '../../../common/data_streams_stats/integration'; -import { NonAggregatableDatasets } from '../../../common/api_types'; +import { DataStreamDocsStat, NonAggregatableDatasets } from '../../../common/api_types'; export type DataStreamsStatsServiceSetup = void; @@ -30,7 +30,8 @@ export interface IDataStreamsStatsClient { getDataStreamsStats(params?: GetDataStreamsStatsQuery): Promise; getDataStreamsDegradedStats( params?: GetDataStreamsDegradedDocsStatsQuery - ): Promise; + ): Promise; + getDataStreamsTotalDocs(params: GetDataStreamsTotalDocsQuery): Promise; getIntegrations(): Promise; getNonAggregatableDatasets( params: GetNonAggregatableDataStreamsParams diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts index 41cfa859ec977..7c77fe9d59422 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts @@ -37,7 +37,8 @@ export const DEFAULT_CONTEXT: DefaultDatasetQualityControllerState = { canViewIntegrations: true, }, dataStreamStats: [], - degradedDocStats: DEFAULT_DICTIONARY_TYPE, + degradedDocStats: [], + totalDocsStats: DEFAULT_DICTIONARY_TYPE, filters: { inactive: true, fullNames: false, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts index a21cc85aac449..0dea80104245f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/notifications.ts @@ -7,6 +7,7 @@ import { IToasts } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; +import { DataStreamType } from '../../../../common/types'; export const fetchDatasetStatsFailedNotifier = (toasts: IToasts, error: Error) => { toasts.addDanger({ @@ -26,6 +27,18 @@ export const fetchDegradedStatsFailedNotifier = (toasts: IToasts, error: Error) }); }; +export const fetchTotalDocsFailedNotifier = (toasts: IToasts, error: Error, meta: any) => { + const dataStreamType = meta._event.origin as DataStreamType; + + toasts.addDanger({ + title: i18n.translate('xpack.datasetQuality.fetchTotalDocsFailed', { + defaultMessage: "We couldn't get total docs information for {dataStreamType}.", + values: { dataStreamType }, + }), + text: error.message, + }); +}; + export const fetchIntegrationsFailedNotifier = (toasts: IToasts, error: Error) => { toasts.addDanger({ title: i18n.translate('xpack.datasetQuality.fetchIntegrationsFailed', { diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts index a803d73448263..1217e52894ce7 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts @@ -8,12 +8,13 @@ import { IToasts } from '@kbn/core/public'; import { getDateISORange } from '@kbn/timerange'; import { assign, createMachine, DoneInvokeEvent, InterpreterFrom } from 'xstate'; -import { DataStreamStat, NonAggregatableDatasets } from '../../../../common/api_types'; -import { KNOWN_TYPES } from '../../../../common/constants'; import { - DataStreamDegradedDocsStatServiceResponse, - DataStreamStatServiceResponse, -} from '../../../../common/data_streams_stats'; + DataStreamDocsStat, + DataStreamStat, + NonAggregatableDatasets, +} from '../../../../common/api_types'; +import { KNOWN_TYPES } from '../../../../common/constants'; +import { DataStreamStatServiceResponse } from '../../../../common/data_streams_stats'; import { Integration } from '../../../../common/data_streams_stats/integration'; import { DataStreamType } from '../../../../common/types'; import { IDataStreamsStatsClient } from '../../../services/data_streams_stats'; @@ -24,6 +25,7 @@ import { fetchDatasetStatsFailedNotifier, fetchDegradedStatsFailedNotifier, fetchIntegrationsFailedNotifier, + fetchTotalDocsFailedNotifier, } from './notifications'; import { DatasetQualityControllerContext, @@ -92,34 +94,69 @@ export const createPureDatasetQualityControllerStateMachine = ( initial: 'fetching', states: { fetching: { - ...generateInvokePerType({ + invoke: { src: 'loadDegradedDocs', + onDone: { + target: 'loaded', + actions: ['storeDegradedDocStats', 'storeDatasets'], + }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'loaded', + actions: ['notifyFetchDegradedStatsFailed'], + }, + ], + }, + }, + loaded: {}, + unauthorized: { type: 'final' }, + }, + on: { + UPDATE_TIME_RANGE: { + target: 'degradedDocs.fetching', + actions: ['storeTimeRange'], + }, + REFRESH_DATA: { + target: 'degradedDocs.fetching', + }, + }, + }, + docsStats: { + initial: 'fetching', + states: { + fetching: { + ...generateInvokePerType({ + src: 'loadDataStreamDocsStats', }), }, loaded: {}, unauthorized: { type: 'final' }, }, on: { - SAVE_DEGRADED_DOCS_STATS: { - target: 'degradedDocs.loaded', - actions: ['storeDegradedDocStats', 'storeDatasets'], + SAVE_TOTAL_DOCS_STATS: { + target: 'docsStats.loaded', + actions: ['storeTotalDocStats', 'storeDatasets'], }, - NOTIFY_DEGRADED_DOCS_STATS_FAILED: [ + NOTIFY_TOTAL_DOCS_STATS_FAILED: [ { - target: 'degradedDocs.unauthorized', + target: 'docsStats.unauthorized', cond: 'checkIfActionForbidden', }, { - target: 'degradedDocs.loaded', - actions: ['notifyFetchDegradedStatsFailed'], + target: 'docsStats.loaded', + actions: ['notifyFetchTotalDocsFailed'], }, ], UPDATE_TIME_RANGE: { - target: 'degradedDocs.fetching', + target: 'docsStats.fetching', actions: ['storeTimeRange'], }, REFRESH_DATA: { - target: 'degradedDocs.fetching', + target: 'docsStats.fetching', }, }, }, @@ -329,18 +366,21 @@ export const createPureDatasetQualityControllerStateMachine = ( }; } ), - storeDegradedDocStats: assign( - (context, event: DoneInvokeEvent, meta) => { + storeTotalDocStats: assign( + (context, event: DoneInvokeEvent, meta) => { const type = meta._event.origin as DataStreamType; return { - degradedDocStats: { - ...context.degradedDocStats, + totalDocsStats: { + ...context.totalDocsStats, [type]: event.data, }, }; } ), + storeDegradedDocStats: assign((_context, event: DoneInvokeEvent) => ({ + degradedDocStats: event.data, + })), storeNonAggregatableDatasets: assign( (_context, event: DoneInvokeEvent) => ({ nonAggregatableDatasets: event.data.datasets, @@ -364,7 +404,8 @@ export const createPureDatasetQualityControllerStateMachine = ( datasets: generateDatasets( context.dataStreamStats, context.degradedDocStats, - context.integrations + context.integrations, + context.totalDocsStats ), } : {}; @@ -404,6 +445,8 @@ export const createDatasetQualityControllerStateMachine = ({ fetchNonAggregatableDatasetsFailedNotifier(toasts, event.data), notifyFetchIntegrationsFailed: (_context, event: DoneInvokeEvent) => fetchIntegrationsFailedNotifier(toasts, event.data), + notifyFetchTotalDocsFailed: (_context, event: DoneInvokeEvent, meta) => + fetchTotalDocsFailedNotifier(toasts, event.data, meta), }, services: { loadDataStreamStats: (context, _event) => @@ -411,32 +454,41 @@ export const createDatasetQualityControllerStateMachine = ({ types: context.filters.types as DataStreamType[], datasetQuery: context.filters.query, }), - loadDegradedDocs: + loadDataStreamDocsStats: (context, _event, { data: { type } }) => async (send) => { try { const { startDate: start, endDate: end } = getDateISORange(context.filters.timeRange); - const degradedDocsStats = await (isTypeSelected(type, context) - ? dataStreamStatsClient.getDataStreamsDegradedStats({ + const totalDocsStats = await (isTypeSelected(type, context) + ? dataStreamStatsClient.getDataStreamsTotalDocs({ type, - datasetQuery: context.filters.query, start, end, }) : Promise.resolve([])); send({ - type: 'SAVE_DEGRADED_DOCS_STATS', - data: degradedDocsStats, + type: 'SAVE_TOTAL_DOCS_STATS', + data: totalDocsStats, }); } catch (e) { send({ - type: 'NOTIFY_DEGRADED_DOCS_STATS_FAILED', + type: 'NOTIFY_TOTAL_DOCS_STATS_FAILED', data: e, }); } }, + loadDegradedDocs: (context) => { + const { startDate: start, endDate: end } = getDateISORange(context.filters.timeRange); + + return dataStreamStatsClient.getDataStreamsDegradedStats({ + types: context.filters.types as DataStreamType[], + datasetQuery: context.filters.query, + start, + end, + }); + }, loadNonAggregatableDatasets: (context) => { const { startDate: start, endDate: end } = getDateISORange(context.filters.timeRange); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts index a5e03cfb480ff..de7fdbf9fbd77 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts @@ -6,16 +6,18 @@ */ import { DoneInvokeEvent } from 'xstate'; -import { DatasetUserPrivileges, NonAggregatableDatasets } from '../../../../common/api_types'; import { - DataStreamDegradedDocsStatServiceResponse, + DataStreamDocsStat, + DatasetUserPrivileges, + NonAggregatableDatasets, +} from '../../../../common/api_types'; +import { DataStreamDetails, DataStreamStat, DataStreamStatServiceResponse, DataStreamStatType, } from '../../../../common/data_streams_stats'; import { Integration } from '../../../../common/data_streams_stats/integration'; -import { DegradedDocsStat } from '../../../../common/data_streams_stats/malformed_docs_stat'; import { DataStreamType, QualityIndicators, @@ -50,8 +52,12 @@ export interface WithDataStreamStats { dataStreamStats: DataStreamStatType[]; } +export interface WithTotalDocs { + totalDocsStats: DictionaryType; +} + export interface WithDegradedDocs { - degradedDocStats: DictionaryType; + degradedDocStats: DataStreamDocsStat[]; } export interface WithNonAggregatableDatasets { @@ -68,6 +74,7 @@ export interface WithIntegrations { export type DefaultDatasetQualityControllerState = WithTableOptions & WithDataStreamStats & + WithTotalDocs & WithDegradedDocs & WithDatasets & WithFilters & @@ -146,7 +153,7 @@ export type DatasetQualityControllerEvent = type: 'UPDATE_TYPES'; types: DataStreamType[]; } - | DoneInvokeEvent + | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent | DoneInvokeEvent diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts b/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts index 6f2e46baacf8c..b75c74c2fd728 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { indexNameToDataStreamParts } from '../../common/utils'; -import { Integration } from '../../common/data_streams_stats/integration'; -import { generateDatasets } from './generate_datasets'; import { DataStreamStatType } from '../../common/data_streams_stats'; +import { Integration } from '../../common/data_streams_stats/integration'; import { DEFAULT_DICTIONARY_TYPE } from '../state_machines/dataset_quality_controller'; +import { generateDatasets } from './generate_datasets'; describe('generateDatasets', () => { const integrations: Integration[] = [ @@ -41,6 +40,7 @@ describe('generateDatasets', () => { lastActivity: 1712911241117, size: '82.1kb', sizeBytes: 84160, + totalDocs: 100, integration: 'system', userPrivileges: { canMonitor: true, @@ -51,182 +51,337 @@ describe('generateDatasets', () => { lastActivity: 1712911241117, size: '62.5kb', sizeBytes: 64066, + totalDocs: 100, userPrivileges: { canMonitor: true, }, }, ]; - const degradedDocs = { + const totalDocs = { ...DEFAULT_DICTIONARY_TYPE, logs: [ { dataset: 'logs-system.application-default', - percentage: 0, - count: 0, - docsCount: 0, - quality: 'good' as const, + count: 100, }, { dataset: 'logs-synth-default', - percentage: 11.320754716981131, - count: 6, - docsCount: 0, - quality: 'poor' as const, + count: 100, }, ], }; - it('merges integrations information with dataStreamStats', () => { - const datasets = generateDatasets(dataStreamStats, DEFAULT_DICTIONARY_TYPE, integrations); + const degradedDocs = [ + { + dataset: 'logs-system.application-default', + count: 0, + }, + { + dataset: 'logs-synth-default', + count: 6, + }, + ]; + + it('merges integrations information with dataStreamStats and degradedDocs', () => { + const datasets = generateDatasets(dataStreamStats, degradedDocs, integrations, totalDocs); expect(datasets).toEqual([ { - ...dataStreamStats[0], - name: indexNameToDataStreamParts(dataStreamStats[0].name).dataset, - namespace: indexNameToDataStreamParts(dataStreamStats[0].name).namespace, - title: - integrations[0].datasets[indexNameToDataStreamParts(dataStreamStats[0].name).dataset], - type: indexNameToDataStreamParts(dataStreamStats[0].name).type, - rawName: dataStreamStats[0].name, + name: 'system.application', + type: 'logs', + namespace: 'default', + title: 'Windows Application Events', + rawName: 'logs-system.application-default', + lastActivity: 1712911241117, + size: '82.1kb', + sizeBytes: 84160, integration: integrations[0], + totalDocs: 100, + userPrivileges: { + canMonitor: true, + }, + docsInTimeRange: 100, + quality: 'good', + degradedDocs: { + percentage: 0, + count: 0, + }, + }, + { + name: 'synth', + type: 'logs', + namespace: 'default', + title: 'synth', + rawName: 'logs-synth-default', + lastActivity: 1712911241117, + size: '62.5kb', + sizeBytes: 64066, + integration: undefined, + totalDocs: 100, + userPrivileges: { + canMonitor: true, + }, + docsInTimeRange: 100, + quality: 'poor', degradedDocs: { - percentage: degradedDocs.logs[0].percentage, - count: degradedDocs.logs[0].count, - docsCount: degradedDocs.logs[0].docsCount, - quality: degradedDocs.logs[0].quality, + count: 6, + percentage: 6, }, }, + ]); + }); + + it('merges integrations information with dataStreamStats and degradedDocs when no docs in timerange', () => { + const datasets = generateDatasets( + dataStreamStats, + degradedDocs, + integrations, + DEFAULT_DICTIONARY_TYPE + ); + + expect(datasets).toEqual([ { - ...dataStreamStats[1], - name: indexNameToDataStreamParts(dataStreamStats[1].name).dataset, - namespace: indexNameToDataStreamParts(dataStreamStats[1].name).namespace, - title: indexNameToDataStreamParts(dataStreamStats[1].name).dataset, - type: indexNameToDataStreamParts(dataStreamStats[1].name).type, - rawName: dataStreamStats[1].name, + name: 'system.application', + type: 'logs', + namespace: 'default', + title: 'Windows Application Events', + rawName: 'logs-system.application-default', + lastActivity: 1712911241117, + size: '82.1kb', + sizeBytes: 84160, + integration: integrations[0], + totalDocs: 100, + userPrivileges: { + canMonitor: true, + }, + docsInTimeRange: 0, + quality: 'good', degradedDocs: { + percentage: 0, count: 0, + }, + }, + { + name: 'synth', + type: 'logs', + namespace: 'default', + title: 'synth', + rawName: 'logs-synth-default', + lastActivity: 1712911241117, + size: '62.5kb', + sizeBytes: 64066, + integration: undefined, + totalDocs: 100, + userPrivileges: { + canMonitor: true, + }, + docsInTimeRange: 0, + quality: 'good', + degradedDocs: { + count: 6, percentage: 0, - docsCount: 0, - quality: 'good', }, }, ]); }); it('merges integrations information with degradedDocs', () => { - const datasets = generateDatasets(undefined, degradedDocs, integrations); + const datasets = generateDatasets([], degradedDocs, integrations, totalDocs); expect(datasets).toEqual([ { - rawName: degradedDocs.logs[0].dataset, - name: indexNameToDataStreamParts(degradedDocs.logs[0].dataset).dataset, - type: indexNameToDataStreamParts(degradedDocs.logs[0].dataset).type, + name: 'system.application', + type: 'logs', + namespace: 'default', + title: 'Windows Application Events', + rawName: 'logs-system.application-default', + lastActivity: undefined, + size: undefined, + sizeBytes: undefined, + integration: integrations[0], + totalDocs: undefined, + userPrivileges: undefined, + docsInTimeRange: 100, + quality: 'good', + degradedDocs: { + percentage: 0, + count: 0, + }, + }, + { + name: 'synth', + type: 'logs', + namespace: 'default', + title: 'synth', + rawName: 'logs-synth-default', lastActivity: undefined, size: undefined, sizeBytes: undefined, + integration: undefined, + totalDocs: undefined, userPrivileges: undefined, - namespace: indexNameToDataStreamParts(degradedDocs.logs[0].dataset).namespace, - title: - integrations[0].datasets[ - indexNameToDataStreamParts(degradedDocs.logs[0].dataset).dataset - ], + docsInTimeRange: 100, + quality: 'poor', + degradedDocs: { + count: 6, + percentage: 6, + }, + }, + ]); + }); + + it('merges integrations information with degradedDocs and totalDocs', () => { + const datasets = generateDatasets([], degradedDocs, integrations, { + ...totalDocs, + logs: [...totalDocs.logs, { dataset: 'logs-another-default', count: 100 }], + }); + + expect(datasets).toEqual([ + { + name: 'system.application', + type: 'logs', + namespace: 'default', + title: 'Windows Application Events', + rawName: 'logs-system.application-default', + lastActivity: undefined, + size: undefined, + sizeBytes: undefined, integration: integrations[0], + totalDocs: undefined, + userPrivileges: undefined, + docsInTimeRange: 100, + quality: 'good', degradedDocs: { - percentage: degradedDocs.logs[0].percentage, - count: degradedDocs.logs[0].count, - docsCount: degradedDocs.logs[0].docsCount, - quality: degradedDocs.logs[0].quality, + percentage: 0, + count: 0, }, }, { - rawName: degradedDocs.logs[1].dataset, - name: indexNameToDataStreamParts(degradedDocs.logs[1].dataset).dataset, - type: indexNameToDataStreamParts(degradedDocs.logs[1].dataset).type, + name: 'synth', + type: 'logs', + namespace: 'default', + title: 'synth', + rawName: 'logs-synth-default', lastActivity: undefined, size: undefined, sizeBytes: undefined, + integration: undefined, + totalDocs: undefined, userPrivileges: undefined, - namespace: indexNameToDataStreamParts(degradedDocs.logs[1].dataset).namespace, - title: indexNameToDataStreamParts(degradedDocs.logs[1].dataset).dataset, + docsInTimeRange: 100, + quality: 'poor', + degradedDocs: { + count: 6, + percentage: 6, + }, + }, + { + name: 'another', + type: 'logs', + namespace: 'default', + title: 'another', + rawName: 'logs-another-default', + lastActivity: undefined, + size: undefined, + sizeBytes: undefined, integration: undefined, + totalDocs: undefined, + userPrivileges: undefined, + docsInTimeRange: 100, + quality: 'good', degradedDocs: { - percentage: degradedDocs.logs[1].percentage, - count: degradedDocs.logs[1].count, - docsCount: degradedDocs.logs[1].docsCount, - quality: degradedDocs.logs[1].quality, + percentage: 0, + count: 0, }, }, ]); }); - it('merges integrations information with dataStreamStats and degradedDocs', () => { - const datasets = generateDatasets(dataStreamStats, degradedDocs, integrations); + it('merges integrations information with dataStreamStats', () => { + const datasets = generateDatasets(dataStreamStats, [], integrations, totalDocs); expect(datasets).toEqual([ { - ...dataStreamStats[0], - name: indexNameToDataStreamParts(dataStreamStats[0].name).dataset, - namespace: indexNameToDataStreamParts(dataStreamStats[0].name).namespace, - title: - integrations[0].datasets[indexNameToDataStreamParts(dataStreamStats[0].name).dataset], - type: indexNameToDataStreamParts(dataStreamStats[0].name).type, - rawName: dataStreamStats[0].name, + name: 'system.application', + type: 'logs', + namespace: 'default', + title: 'Windows Application Events', + rawName: 'logs-system.application-default', + lastActivity: 1712911241117, + size: '82.1kb', + sizeBytes: 84160, integration: integrations[0], + totalDocs: 100, + userPrivileges: { + canMonitor: true, + }, + quality: 'good', + docsInTimeRange: 100, degradedDocs: { - percentage: degradedDocs.logs[0].percentage, - count: degradedDocs.logs[0].count, - docsCount: degradedDocs.logs[0].docsCount, - quality: degradedDocs.logs[0].quality, + count: 0, + percentage: 0, }, }, { - ...dataStreamStats[1], - name: indexNameToDataStreamParts(dataStreamStats[1].name).dataset, - namespace: indexNameToDataStreamParts(dataStreamStats[1].name).namespace, - title: indexNameToDataStreamParts(dataStreamStats[1].name).dataset, - type: indexNameToDataStreamParts(dataStreamStats[1].name).type, - rawName: dataStreamStats[1].name, + name: 'synth', + type: 'logs', + namespace: 'default', + title: 'synth', + rawName: 'logs-synth-default', + lastActivity: 1712911241117, + size: '62.5kb', + sizeBytes: 64066, + integration: undefined, + totalDocs: 100, + userPrivileges: { + canMonitor: true, + }, + quality: 'good', + docsInTimeRange: 100, degradedDocs: { - percentage: degradedDocs.logs[1].percentage, - count: degradedDocs.logs[1].count, - docsCount: degradedDocs.logs[1].docsCount, - quality: degradedDocs.logs[1].quality, + count: 0, + percentage: 0, }, }, ]); }); it('merges integration information with dataStreamStats when dataset is not an integration default one', () => { - const dataset = 'logs-system.custom-default'; - const nonDefaultDataset = { - name: dataset, + name: 'logs-system.custom-default', lastActivity: 1712911241117, size: '82.1kb', sizeBytes: 84160, + totalDocs: 100, integration: 'system', userPrivileges: { canMonitor: true, }, }; - const datasets = generateDatasets([nonDefaultDataset], DEFAULT_DICTIONARY_TYPE, integrations); + const datasets = generateDatasets([nonDefaultDataset], [], integrations, totalDocs); expect(datasets).toEqual([ { - ...nonDefaultDataset, - title: indexNameToDataStreamParts(dataset).dataset, - name: indexNameToDataStreamParts(dataset).dataset, - namespace: indexNameToDataStreamParts(dataset).namespace, - type: indexNameToDataStreamParts(dataset).type, - rawName: nonDefaultDataset.name, + name: 'system.custom', + type: 'logs', + namespace: 'default', + title: 'system.custom', + rawName: 'logs-system.custom-default', + lastActivity: 1712911241117, + size: '82.1kb', + sizeBytes: 84160, integration: integrations[0], + userPrivileges: { + canMonitor: true, + }, + quality: 'good', + totalDocs: 100, + docsInTimeRange: 0, degradedDocs: { count: 0, percentage: 0, - docsCount: 0, - quality: 'good', }, }, ]); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.ts b/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.ts index fb479198bbac3..8e9f2f3db7083 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.ts @@ -5,23 +5,20 @@ * 2.0. */ +import { DEFAULT_DEGRADED_DOCS } from '../../common/constants'; +import { DataStreamDocsStat } from '../../common/api_types'; import { DataStreamStatType } from '../../common/data_streams_stats/types'; import { mapPercentageToQuality } from '../../common/utils'; import { Integration } from '../../common/data_streams_stats/integration'; import { DataStreamStat } from '../../common/data_streams_stats/data_stream_stat'; -import { DegradedDocsStat } from '../../common/data_streams_stats/malformed_docs_stat'; import { DictionaryType } from '../state_machines/dataset_quality_controller/src/types'; import { flattenStats } from './flatten_stats'; - export function generateDatasets( dataStreamStats: DataStreamStatType[] = [], - degradedDocStats: DictionaryType, - integrations: Integration[] + degradedDocStats: DataStreamDocsStat[] = [], + integrations: Integration[], + totalDocsStats: DictionaryType ): DataStreamStat[] { - if (!dataStreamStats.length && !integrations.length) { - return []; - } - const { datasetIntegrationMap, integrationsMap, @@ -50,35 +47,42 @@ export function generateDatasets( { datasetIntegrationMap: {}, integrationsMap: {} } ); - const degradedDocs = flattenStats(degradedDocStats); - - if (!dataStreamStats.length) { - return degradedDocs.map((degradedDocStat) => - DataStreamStat.fromDegradedDocStat({ degradedDocStat, datasetIntegrationMap }) - ); - } + const totalDocs = flattenStats(totalDocsStats); + const totalDocsMap: Record = + Object.fromEntries(totalDocs.map(({ dataset, count }) => [dataset, count])); const degradedMap: Record< - DegradedDocsStat['dataset'], + DataStreamDocsStat['dataset'], { - percentage: DegradedDocsStat['percentage']; - count: DegradedDocsStat['count']; - docsCount: DegradedDocsStat['docsCount']; - quality: DegradedDocsStat['quality']; + percentage: number; + count: DataStreamDocsStat['count']; } - > = degradedDocs.reduce( - (degradedMapAcc, { dataset, percentage, count, docsCount }) => + > = degradedDocStats.reduce( + (degradedMapAcc, { dataset, count }) => Object.assign(degradedMapAcc, { [dataset]: { - percentage, count, - docsCount, - quality: mapPercentageToQuality(percentage), + percentage: DataStreamStat.calculatePercentage({ + totalDocs: totalDocsMap[dataset], + count, + }), }, }), {} ); + if (!dataStreamStats.length) { + // We want to pick up all datasets even when they don't have degraded docs + const dataStreams = [...new Set([...Object.keys(totalDocsMap), ...Object.keys(degradedMap)])]; + return dataStreams.map((dataset) => + DataStreamStat.fromDegradedDocStat({ + degradedDocStat: { dataset, ...(degradedMap[dataset] || DEFAULT_DEGRADED_DOCS) }, + datasetIntegrationMap, + totalDocs: totalDocsMap[dataset] ?? 0, + }) + ); + } + return dataStreamStats?.map((dataStream) => { const dataset = DataStreamStat.create(dataStream); @@ -89,6 +93,10 @@ export function generateDatasets( datasetIntegrationMap[dataset.name]?.integration ?? integrationsMap[dataStream.integration ?? ''], degradedDocs: degradedMap[dataset.rawName] || dataset.degradedDocs, + docsInTimeRange: totalDocsMap[dataset.rawName] ?? 0, + quality: mapPercentageToQuality( + (degradedMap[dataset.rawName] || dataset.degradedDocs).percentage + ), }; }); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_dataset_aggregated_paginated_results.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_dataset_aggregated_paginated_results.ts new file mode 100644 index 0000000000000..062dcd2f16cf7 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_dataset_aggregated_paginated_results.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core/server'; +import { rangeQuery } from '@kbn/observability-plugin/server'; +import { QueryDslBoolQuery } from '@elastic/elasticsearch/lib/api/types'; +import { DataStreamDocsStat } from '../../../common/api_types'; +import { createDatasetQualityESClient } from '../../utils'; + +interface Dataset { + type: string; + dataset: string; + namespace: string; +} + +const SIZE_LIMIT = 10000; + +export async function getAggregatedDatasetPaginatedResults(options: { + esClient: ElasticsearchClient; + index: string; + start: number; + end: number; + query?: QueryDslBoolQuery; + after?: Dataset; + prevResults?: DataStreamDocsStat[]; +}): Promise { + const { esClient, index, query, start, end, after, prevResults = [] } = options; + + const datasetQualityESClient = createDatasetQualityESClient(esClient); + + const aggs = (afterKey?: Dataset) => ({ + datasets: { + composite: { + ...(afterKey ? { after: afterKey } : {}), + size: SIZE_LIMIT, + sources: [ + { type: { terms: { field: 'data_stream.type' } } }, + { dataset: { terms: { field: 'data_stream.dataset' } } }, + { namespace: { terms: { field: 'data_stream.namespace' } } }, + ], + }, + }, + }); + + const bool = { + ...query, + filter: [ + ...(query?.filter ? (Array.isArray(query.filter) ? query.filter : [query.filter]) : []), + ...[...rangeQuery(start, end)], + ], + }; + + const response = await datasetQualityESClient.search({ + index, + size: 0, + query: { + bool, + }, + aggs: aggs(after), + }); + + const currResults = + response.aggregations?.datasets.buckets.map((bucket) => ({ + dataset: `${bucket.key.type}-${bucket.key.dataset}-${bucket.key.namespace}`, + count: bucket.doc_count, + })) ?? []; + + const results = [...prevResults, ...currResults]; + + if ( + response.aggregations?.datasets.after_key && + response.aggregations?.datasets.buckets.length === SIZE_LIMIT + ) { + return getAggregatedDatasetPaginatedResults({ + esClient, + index, + start, + end, + after: + (response.aggregations?.datasets.after_key as { + type: string; + dataset: string; + namespace: string; + }) || after, + prevResults: results, + }); + } + + return results; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts index 454fdb7e1a8b8..48b50c4b8680d 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts @@ -6,161 +6,37 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; -import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; -import { DEFAULT_DATASET_TYPE } from '../../../common/constants'; +import { streamPartsToIndexPattern } from '../../../common/utils'; import { DataStreamType } from '../../../common/types'; -import { DegradedDocs } from '../../../common/api_types'; -import { - DATA_STREAM_DATASET, - DATA_STREAM_NAMESPACE, - DATA_STREAM_TYPE, - _IGNORED, -} from '../../../common/es_fields'; -import { createDatasetQualityESClient, wildcardQuery } from '../../utils'; - -interface ResultBucket { - dataset: string; - count: number; -} - -const SIZE_LIMIT = 10000; +import { DataStreamDocsStat } from '../../../common/api_types'; +import { _IGNORED } from '../../../common/es_fields'; +import { getAggregatedDatasetPaginatedResults } from './get_dataset_aggregated_paginated_results'; export async function getDegradedDocsPaginated(options: { esClient: ElasticsearchClient; - type?: DataStreamType; + types: DataStreamType[]; + datasetQuery?: string; start: number; end: number; - datasetQuery?: string; - after?: { - degradedDocs?: { dataset: string; namespace: string }; - docsCount?: { dataset: string; namespace: string }; - }; - prevResults?: { degradedDocs: ResultBucket[]; docsCount: ResultBucket[] }; -}): Promise { - const { +}): Promise { + const { esClient, types, datasetQuery, start, end } = options; + + const datasetNames = datasetQuery + ? [datasetQuery] + : types.map((type) => + streamPartsToIndexPattern({ + typePattern: type, + datasetPattern: '*-*', + }) + ); + + return await getAggregatedDatasetPaginatedResults({ esClient, - type = DEFAULT_DATASET_TYPE, - datasetQuery, start, end, - after, - prevResults = { degradedDocs: [], docsCount: [] }, - } = options; - - const datasetQualityESClient = createDatasetQualityESClient(esClient); - - const datasetFilter = { - ...(datasetQuery - ? { - should: [ - ...wildcardQuery(DATA_STREAM_DATASET, datasetQuery), - ...wildcardQuery(DATA_STREAM_NAMESPACE, datasetQuery), - ], - minimum_should_match: 1, - } - : {}), - }; - - const otherFilters = [...rangeQuery(start, end), ...termQuery(DATA_STREAM_TYPE, type)]; - - const aggs = (afterKey?: { dataset: string; namespace: string }) => ({ - datasets: { - composite: { - ...(afterKey ? { after: afterKey } : {}), - size: SIZE_LIMIT, - sources: [ - { dataset: { terms: { field: 'data_stream.dataset' } } }, - { namespace: { terms: { field: 'data_stream.namespace' } } }, - ], - }, + index: datasetNames.join(','), + query: { + must: { exists: { field: _IGNORED } }, }, }); - - const response = await datasetQualityESClient.msearch({ index: `${type}-*-*` }, [ - // degraded docs per dataset - { - size: 0, - query: { - bool: { - ...datasetFilter, - filter: otherFilters, - must: { exists: { field: _IGNORED } }, - }, - }, - aggs: aggs(after?.degradedDocs), - }, - // total docs per dataset - { - size: 0, - query: { - bool: { - ...datasetFilter, - filter: otherFilters, - }, - }, - aggs: aggs(after?.docsCount), - }, - ]); - const [degradedDocsResponse, totalDocsResponse] = response.responses; - - const currDegradedDocs = - degradedDocsResponse.aggregations?.datasets.buckets.map((bucket) => ({ - dataset: `${type}-${bucket.key.dataset}-${bucket.key.namespace}`, - count: bucket.doc_count, - })) ?? []; - - const degradedDocs = [...prevResults.degradedDocs, ...currDegradedDocs]; - - const currTotalDocs = - totalDocsResponse.aggregations?.datasets.buckets.map((bucket) => ({ - dataset: `${type}-${bucket.key.dataset}-${bucket.key.namespace}`, - count: bucket.doc_count, - })) ?? []; - - const docsCount = [...prevResults.docsCount, ...currTotalDocs]; - - if ( - totalDocsResponse.aggregations?.datasets.after_key && - totalDocsResponse.aggregations?.datasets.buckets.length === SIZE_LIMIT - ) { - return getDegradedDocsPaginated({ - esClient, - type, - start, - end, - datasetQuery, - after: { - degradedDocs: - (degradedDocsResponse.aggregations?.datasets.after_key as { - dataset: string; - namespace: string; - }) || after?.degradedDocs, - docsCount: - (totalDocsResponse.aggregations?.datasets.after_key as { - dataset: string; - namespace: string; - }) || after?.docsCount, - }, - prevResults: { degradedDocs, docsCount }, - }); - } - - const degradedDocsMap = degradedDocs.reduce( - (acc, curr) => ({ - ...acc, - [curr.dataset]: curr.count, - }), - {} - ); - - return docsCount.map((curr) => { - const degradedDocsCount = degradedDocsMap[curr.dataset as keyof typeof degradedDocsMap] || 0; - - return { - ...curr, - docsCount: curr.count, - count: degradedDocsCount, - percentage: (degradedDocsCount / curr.count) * 100, - }; - }); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts index 41ba3ee8c7299..3a60f0b9a8ef3 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts @@ -10,12 +10,12 @@ import { DataStreamDetails, DataStreamSettings, DataStreamStat, - DegradedDocs, NonAggregatableDatasets, DegradedFieldResponse, DatasetUserPrivileges, DegradedFieldValues, DegradedFieldAnalysis, + DataStreamDocsStat, UpdateFieldLimitResponse, DataStreamRolloverResponse, } from '../../../common/api_types'; @@ -31,6 +31,7 @@ import { getDegradedFields } from './get_degraded_fields'; import { getDegradedFieldValues } from './get_degraded_field_values'; import { analyzeDegradedField } from './get_degraded_field_analysis'; import { getDataStreamsMeteringStats } from './get_data_streams_metering_stats'; +import { getAggregatedDatasetPaginatedResults } from './get_dataset_aggregated_paginated_results'; import { updateFieldLimit } from './update_field_limit'; import { createDatasetQualityESClient } from '../../utils'; @@ -97,7 +98,7 @@ const degradedDocsRoute = createDatasetQualityServerRoute({ params: t.type({ query: t.intersection([ rangeRt, - typeRt, + t.type({ types: typesRt }), t.partial({ datasetQuery: t.string, }), @@ -107,19 +108,13 @@ const degradedDocsRoute = createDatasetQualityServerRoute({ tags: [], }, async handler(resources): Promise<{ - degradedDocs: DegradedDocs[]; + degradedDocs: DataStreamDocsStat[]; }> { const { context, params } = resources; const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asCurrentUser; - await datasetQualityPrivileges.throwIfCannotReadDataset( - esClient, - params.query.type, - params.query.datasetQuery - ); - const degradedDocs = await getDegradedDocsPaginated({ esClient, ...params.query, @@ -131,6 +126,39 @@ const degradedDocsRoute = createDatasetQualityServerRoute({ }, }); +const totalDocsRoute = createDatasetQualityServerRoute({ + endpoint: 'GET /internal/dataset_quality/data_streams/total_docs', + params: t.type({ + query: t.intersection([rangeRt, typeRt]), + }), + options: { + tags: [], + }, + async handler(resources): Promise<{ + totalDocs: DataStreamDocsStat[]; + }> { + const { context, params } = resources; + const coreContext = await context.core; + + const esClient = coreContext.elasticsearch.client.asCurrentUser; + + await datasetQualityPrivileges.throwIfCannotReadDataset(esClient, params.query.type); + + const { type, start, end } = params.query; + + const totalDocs = await getAggregatedDatasetPaginatedResults({ + esClient, + start, + end, + index: `${type}-*-*`, + }); + + return { + totalDocs, + }; + }, +}); + const nonAggregatableDatasetsRoute = createDatasetQualityServerRoute({ endpoint: 'GET /internal/dataset_quality/data_streams/non_aggregatable', params: t.type({ @@ -383,6 +411,7 @@ const rolloverDataStream = createDatasetQualityServerRoute({ export const dataStreamsRouteRepository = { ...statsRoute, ...degradedDocsRoute, + ...totalDocsRoute, ...nonAggregatableDatasetsRoute, ...nonAggregatableDatasetRoute, ...degradedFieldsRoute, diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/data_stream_total_docs.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/data_stream_total_docs.ts new file mode 100644 index 0000000000000..18baaa2d74c34 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/data_stream_total_docs.ts @@ -0,0 +1,132 @@ +/* + * 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 { log, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; + +import { APIClientRequestParamsOf } from '@kbn/dataset-quality-plugin/common/rest'; +import { LogsSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials, SupertestWithRoleScopeType } from '../../../services'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const roleScopedSupertest = getService('roleScopedSupertest'); + const synthtrace = getService('synthtrace'); + const from = '2024-09-20T11:00:00.000Z'; + const to = '2024-09-20T11:01:00.000Z'; + const dataStreamType = 'logs'; + const dataset = 'synth'; + const syntheticsDataset = 'synthetics'; + const namespace = 'default'; + const serviceName = 'my-service'; + const hostName = 'synth-host'; + const dataStreamName = `${dataStreamType}-${dataset}-${namespace}`; + const syntheticsDataStreamName = `${dataStreamType}-${syntheticsDataset}-${namespace}`; + + const endpoint = 'GET /internal/dataset_quality/data_streams/total_docs'; + type ApiParams = APIClientRequestParamsOf['params']['query']; + + async function callApiAs({ + roleScopedSupertestWithCookieCredentials, + apiParams: { type, start, end }, + }: { + roleScopedSupertestWithCookieCredentials: SupertestWithRoleScopeType; + apiParams: ApiParams; + }) { + return roleScopedSupertestWithCookieCredentials + .get(`/internal/dataset_quality/data_streams/total_docs`) + .query({ + type, + start, + end, + }); + } + + describe('DataStream total docs', function () { + let adminRoleAuthc: RoleCredentials; + let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; + let synthtraceLogsEsClient: LogsSynthtraceEsClient; + + before(async () => { + synthtraceLogsEsClient = await synthtrace.createLogsSynthtraceEsClient(); + adminRoleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'admin', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); + + await synthtraceLogsEsClient.index([ + timerange(from, to) + .interval('1m') + .rate(1) + .generator((timestamp) => [ + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(dataset) + .namespace(namespace) + .defaults({ + 'log.file.path': '/my-service.log', + 'service.name': serviceName, + 'host.name': hostName, + }), + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(syntheticsDataset) + .namespace(namespace) + .defaults({ + 'log.file.path': '/my-service.log', + 'service.name': serviceName, + 'host.name': hostName, + }), + ]), + ]); + }); + + after(async () => { + await synthtraceLogsEsClient.clean(); + await samlAuth.invalidateM2mApiKeyWithRoleScope(adminRoleAuthc); + }); + + it('returns number of documents per DataStream', async () => { + const resp = await callApiAs({ + roleScopedSupertestWithCookieCredentials: supertestAdminWithCookieCredentials, + apiParams: { + type: dataStreamType, + start: from, + end: to, + }, + }); + + expect(resp.body.totalDocs.length).to.be(2); + expect(resp.body.totalDocs[0].dataset).to.be(dataStreamName); + expect(resp.body.totalDocs[0].count).to.be(1); + expect(resp.body.totalDocs[1].dataset).to.be(syntheticsDataStreamName); + expect(resp.body.totalDocs[1].count).to.be(1); + }); + + it('returns empty when all documents are outside timeRange', async () => { + const resp = await callApiAs({ + roleScopedSupertestWithCookieCredentials: supertestAdminWithCookieCredentials, + apiParams: { + type: dataStreamType, + start: '2024-09-21T11:00:00.000Z', + end: '2024-09-21T11:01:00.000Z', + }, + }); + + expect(resp.body.totalDocs.length).to.be(0); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/index.ts index 7e555b7a310e1..28133d6c8e613 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/dataset_quality/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) loadTestFile(require.resolve('./data_stream_settings')); loadTestFile(require.resolve('./data_stream_rollover')); loadTestFile(require.resolve('./update_field_limit')); + loadTestFile(require.resolve('./data_stream_total_docs')); }); } diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts index 92aa69610a66d..60aeef1af9c93 100644 --- a/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts @@ -7,8 +7,7 @@ import { log, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; -import { DatasetQualityApiError } from '../../common/dataset_quality_api_supertest'; -import { expectToReject } from '../../utils'; +import rison from '@kbn/rison'; import { DatasetQualityApiClientKey } from '../../common/config'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -24,7 +23,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { endpoint: 'GET /internal/dataset_quality/data_streams/degraded_docs', params: { query: { - type: 'logs', + types: rison.encodeArray(['logs']), start, end, }, @@ -33,13 +32,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } registry.when('Degraded docs', { config: 'basic' }, () => { - describe('authorization', () => { - it('should return a 403 when the user does not have sufficient privileges', async () => { - const err = await expectToReject(() => callApiAs('noAccessUser')); - expect(err.res.status).to.be(403); - }); - }); - describe('and there are log documents', () => { before(async () => { await synthtrace.index([ @@ -75,25 +67,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns stats correctly', async () => { const stats = await callApiAs('datasetQualityMonitorUser'); - expect(stats.body.degradedDocs.length).to.be(2); + expect(stats.body.degradedDocs.length).to.be(1); const degradedDocsStats = stats.body.degradedDocs.reduce( (acc, curr) => ({ ...acc, [curr.dataset]: { - percentage: curr.percentage, count: curr.count, }, }), - {} as Record + {} as Record ); - expect(degradedDocsStats['logs-synth.1-default']).to.eql({ - percentage: 0, - count: 0, - }); expect(degradedDocsStats['logs-synth.2-default']).to.eql({ - percentage: 100, count: 1, }); }); @@ -155,117 +141,45 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns counts and list of datasets correctly', async () => { const stats = await callApiAs('datasetQualityMonitorUser'); - expect(stats.body.degradedDocs.length).to.be(18); + expect(stats.body.degradedDocs.length).to.be(9); const expected = { degradedDocs: [ - { - dataset: 'logs-apache.access-default', - count: 0, - docsCount: 1, - percentage: 0, - }, - { - dataset: 'logs-apache.access-space1', - count: 0, - docsCount: 1, - percentage: 0, - }, - { - dataset: 'logs-apache.access-space2', - count: 0, - docsCount: 1, - percentage: 0, - }, { dataset: 'logs-apache.error-default', count: 1, - docsCount: 2, - percentage: 50, }, { dataset: 'logs-apache.error-space1', count: 1, - docsCount: 2, - percentage: 50, }, { dataset: 'logs-apache.error-space2', count: 1, - docsCount: 2, - percentage: 50, - }, - { - dataset: 'logs-mysql.access-default', - count: 0, - docsCount: 1, - percentage: 0, - }, - { - dataset: 'logs-mysql.access-space1', - count: 0, - docsCount: 1, - percentage: 0, - }, - { - dataset: 'logs-mysql.access-space2', - count: 0, - docsCount: 1, - percentage: 0, }, { dataset: 'logs-mysql.error-default', count: 1, - docsCount: 2, - percentage: 50, }, { dataset: 'logs-mysql.error-space1', count: 1, - docsCount: 2, - percentage: 50, }, { dataset: 'logs-mysql.error-space2', count: 1, - docsCount: 2, - percentage: 50, - }, - { - dataset: 'logs-nginx.access-default', - count: 0, - docsCount: 1, - percentage: 0, - }, - { - dataset: 'logs-nginx.access-space1', - count: 0, - docsCount: 1, - percentage: 0, - }, - { - dataset: 'logs-nginx.access-space2', - count: 0, - docsCount: 1, - percentage: 0, }, { dataset: 'logs-nginx.error-default', count: 1, - docsCount: 2, - percentage: 50, }, { dataset: 'logs-nginx.error-space1', count: 1, - docsCount: 2, - percentage: 50, }, { dataset: 'logs-nginx.error-space2', count: 1, - docsCount: 2, - percentage: 50, }, ], }; diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/total_docs.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/total_docs.spec.ts new file mode 100644 index 0000000000000..71442e1300a2b --- /dev/null +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/total_docs.spec.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { DatasetQualityApiClientKey } from '../../common/config'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { expectToReject } from '../../utils'; +import { DatasetQualityApiError } from '../../common/dataset_quality_api_supertest'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const datasetQualityApiClient = getService('datasetQualityApiClient'); + const start = '2023-12-11T18:00:00.000Z'; + const end = '2023-12-11T18:01:00.000Z'; + + async function callApiAs(user: DatasetQualityApiClientKey) { + return await datasetQualityApiClient[user]({ + endpoint: 'GET /internal/dataset_quality/data_streams/total_docs', + params: { + query: { + type: 'logs', + start, + end, + }, + }, + }); + } + + registry.when('Total docs', { config: 'basic' }, () => { + describe('authorization', () => { + it('should return a 403 when the user does not have sufficient privileges', async () => { + const err = await expectToReject(() => callApiAs('noAccessUser')); + expect(err.res.status).to.be(403); + }); + }); + }); +} From 926861bcbce9a775eda7a389acedf935612b5985 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Wed, 6 Nov 2024 17:38:17 +0100 Subject: [PATCH 24/53] [APM] Foundation for migrating APM tests to deployment agnostic approach (#198775) part of https://github.com/elastic/kibana/issues/193245 closes https://github.com/elastic/kibana/issues/198958 ## Summary This PR lays the foundation to start migrating APM API integration tests to the deployment agnostic approach. ### 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 API tests" ``` 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 API tests" ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- .../agent_explorer/agent_explorer.spec.ts | 28 ++-- .../observability/apm/agent_explorer/index.ts | 15 ++ .../latest_agent_versions.spec.ts | 44 ++++++ .../apis/observability/apm/index.ts | 16 ++ .../observability/infra/infra_asset_count.ts | 1 + .../infra/infra_custom_dashboards.ts | 1 + .../observability/infra/ip_to_hostname.ts | 1 + .../infra/metrics_overview_top.ts | 1 + .../infra/metrics_process_list.ts | 1 + .../infra/metrics_process_list_chart.ts | 1 + .../apis/observability/infra/node_details.ts | 1 + .../apis/observability/infra/services.ts | 4 +- .../apis/observability/infra/snapshot.ts | 1 + .../apis/observability/infra/sources.ts | 1 + .../configs/serverless/oblt.index.ts | 1 + .../configs/stateful/oblt.index.ts | 1 + .../deployment_agnostic/services/apm_api.ts | 137 ++++++++++++++++++ .../deployment_agnostic/services/index.ts | 2 + .../services/synthtrace.ts | 3 +- .../latest_agent_versions.spec.ts | 50 ------- x-pack/test/tsconfig.json | 2 +- 21 files changed, 246 insertions(+), 66 deletions(-) rename x-pack/test/{apm_api_integration/tests => api_integration/deployment_agnostic/apis/observability/apm}/agent_explorer/agent_explorer.spec.ts (91%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts create mode 100644 x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts delete mode 100644 x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts diff --git a/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/agent_explorer.spec.ts similarity index 91% rename from x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/agent_explorer.spec.ts index 95e71167aaab4..95d438ba87cad 100644 --- a/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/agent_explorer.spec.ts @@ -8,13 +8,13 @@ import expect from '@kbn/expect'; import { apm, timerange } from '@kbn/apm-synthtrace-client'; import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; import { keyBy } from 'lodash'; -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'); - 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; @@ -42,18 +42,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - registry.when('Agent explorer when data is not loaded', { config: 'basic', archives: [] }, () => { - it('handles empty state', async () => { - const { status, body } = await callApi(); + describe('Agent explorer', () => { + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const { status, body } = await callApi(); - expect(status).to.be(200); - expect(body.items).to.be.empty(); + expect(status).to.be(200); + expect(body.items).to.be.empty(); + }); }); - }); - registry.when('Agent explorer', { config: 'basic', archives: [] }, () => { describe('when data is loaded', () => { + let apmSynthtraceEsClient: ApmSynthtraceEsClient; + before(async () => { + apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient(); + const serviceOtelJava = apm .service({ name: otelJavaServiceName, 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 new file mode 100644 index 0000000000000..f77b13923930a --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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('agent_explorer', () => { + loadTestFile(require.resolve('./agent_explorer.spec.ts')); + loadTestFile(require.resolve('./latest_agent_versions.spec.ts')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts new file mode 100644 index 0000000000000..7d40993885255 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/agent_explorer/latest_agent_versions.spec.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ElasticApmAgentLatestVersion } from '@kbn/apm-plugin/common/agent_explorer'; +import expect from '@kbn/expect'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const nodeAgentName = 'nodejs'; + const unlistedAgentName = 'unlistedAgent'; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/get_latest_agent_versions', + }); + } + + describe('Agent latest versions when configuration is defined', () => { + it('returns a version when agent is listed in the file', async () => { + const { status, body } = await callApi(); + expect(status).to.be(200); + + const agents = body.data; + + const nodeAgent = agents[nodeAgentName] as ElasticApmAgentLatestVersion; + expect(nodeAgent?.latest_version).not.to.be(undefined); + }); + + it('returns undefined when agent is not listed in the file', async () => { + const { status, body } = await callApi(); + expect(status).to.be(200); + + const agents = body.data; + + // @ts-ignore + const unlistedAgent = agents[unlistedAgentName] as ElasticApmAgentLatestVersion; + expect(unlistedAgent?.latest_version).to.be(undefined); + }); + }); +} 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 new file mode 100644 index 0000000000000..a62c11d40b1af --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 apmApiIntegrationTests({ + loadTestFile, +}: DeploymentAgnosticFtrProviderContext) { + describe('APM', function () { + loadTestFile(require.resolve('./agent_explorer')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_asset_count.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_asset_count.ts index 0c139ccb2023b..c76e7dbff888c 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_asset_count.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_asset_count.ts @@ -48,6 +48,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/logs_and_metrics'); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_custom_dashboards.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_custom_dashboards.ts index 140b0540c7d71..0357d7b1fff7a 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_custom_dashboards.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/infra_custom_dashboards.ts @@ -28,6 +28,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/ip_to_hostname.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/ip_to_hostname.ts index d75c16aacd7cb..084a7ea9ac276 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/ip_to_hostname.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/ip_to_hostname.ts @@ -19,6 +19,7 @@ export default function ipToHostNameTest({ getService }: DeploymentAgnosticFtrPr before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_overview_top.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_overview_top.ts index 8411d0e9d664d..2a1ee8d3e6821 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_overview_top.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_overview_top.ts @@ -25,6 +25,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/7.0.0/hosts'); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list.ts index 7b49950ef9963..c10d5181372b8 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list.ts @@ -24,6 +24,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/metrics_and_apm'); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list_chart.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list_chart.ts index a1b453d5b074a..10f214ea642f8 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list_chart.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/metrics_process_list_chart.ts @@ -25,6 +25,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/metrics_and_apm'); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/node_details.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/node_details.ts index e492e0dc9723d..7452c319be911 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/node_details.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/node_details.ts @@ -36,6 +36,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/pods_only'); }); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/services.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/services.ts index 54a74ff72c26c..b8e47a40a9120 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/services.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/services.ts @@ -106,10 +106,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); - const version = (await synthtrace.apmSynthtraceKibanaClient.installApmPackage()).version; - synthtraceApmClient = await synthtrace.createApmSynthtraceEsClient(version); + synthtraceApmClient = await synthtrace.createApmSynthtraceEsClient(); }); after(async () => { await synthtrace.apmSynthtraceKibanaClient.uninstallApmPackage(); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/snapshot.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/snapshot.ts index 0b32cf5b8ce8c..f3636662f2b54 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/snapshot.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/snapshot.ts @@ -37,6 +37,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); }); after(async () => { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/sources.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/sources.ts index a2f7d7ca591ad..0710c5112b0fe 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/sources.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/infra/sources.ts @@ -39,6 +39,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { before(async () => { supertestWithAdminScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { withInternalHeaders: true, + useCookieHeader: true, }); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await kibanaServer.savedObjects.cleanStandardList(); diff --git a/x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.index.ts b/x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.index.ts index a4c568713063c..6353f871a8078 100644 --- a/x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.index.ts @@ -20,5 +20,6 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) loadTestFile(require.resolve('../../apis/painless_lab')); loadTestFile(require.resolve('../../apis/saved_objects_management')); loadTestFile(require.resolve('../../apis/observability/slo')); + loadTestFile(require.resolve('../../apis/observability/apm')); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.index.ts b/x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.index.ts index 06eaa9cc217b1..c5a3ad90f81f4 100644 --- a/x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) loadTestFile(require.resolve('../../apis/observability/dataset_quality')); loadTestFile(require.resolve('../../apis/observability/slo')); loadTestFile(require.resolve('../../apis/observability/infra')); + loadTestFile(require.resolve('../../apis/observability/apm')); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts new file mode 100644 index 0000000000000..26d92997a6021 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/apm_api.ts @@ -0,0 +1,137 @@ +/* + * 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 { format } from 'url'; +import request from 'superagent'; +import type { + APIReturnType, + APIClientRequestParamsOf, +} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { APIEndpoint } from '@kbn/apm-plugin/server'; +import { formatRequest } from '@kbn/server-route-repository'; +import { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import type { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +const INTERNAL_API_REGEX = /^\S+\s(\/)?internal\/[^\s]*$/; + +type InternalApi = `${string} /internal/${string}`; +interface ExternalEndpointParams { + roleAuthc: RoleCredentials; +} + +type Options = (TEndpoint extends InternalApi + ? {} + : ExternalEndpointParams) & { + type?: 'form-data'; + endpoint: TEndpoint; + spaceId?: string; +} & APIClientRequestParamsOf & { + params?: { query?: { _inspect?: boolean } }; + }; + +function isPublicApi( + options: Options +): options is Options & ExternalEndpointParams { + return !INTERNAL_API_REGEX.test(options.endpoint); +} + +function createApmApiClient({ getService }: DeploymentAgnosticFtrProviderContext, role: string) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + const logger = getService('log'); + + return async ( + options: Options + ): Promise> => { + const { endpoint, type } = options; + + const params = 'params' in options ? (options.params as Record) : {}; + + const credentials = isPublicApi(options) + ? options.roleAuthc.apiKeyHeader + : await samlAuth.getM2MApiCookieCredentialsWithRoleScope(role); + + const headers: Record = { + ...samlAuth.getInternalRequestHeader(), + ...credentials, + }; + + const { method, pathname, version } = formatRequest(endpoint, params.path); + const pathnameWithSpaceId = options.spaceId ? `/s/${options.spaceId}${pathname}` : pathname; + const url = format({ pathname: pathnameWithSpaceId, query: params?.query }); + + logger.debug(`Calling APM API: ${method.toUpperCase()} ${url}`); + + if (version) { + headers['Elastic-Api-Version'] = version; + } + + let res: request.Response; + if (type === 'form-data') { + const fields: Array<[string, any]> = Object.entries(params.body); + const formDataRequest = supertestWithoutAuth[method](url) + .set(headers) + .set('Content-type', 'multipart/form-data'); + + for (const field of fields) { + void formDataRequest.field(field[0], field[1]); + } + + res = await formDataRequest; + } else if (params.body) { + res = await supertestWithoutAuth[method](url).send(params.body).set(headers); + } else { + res = await supertestWithoutAuth[method](url).set(headers); + } + + // supertest doesn't throw on http errors + if (res?.status !== 200) { + throw new ApmApiError(res, endpoint); + } + + return res; + }; +} + +type ApiErrorResponse = Omit & { + body: { + statusCode: number; + error: string; + message: string; + attributes: object; + }; +}; + +export type ApmApiSupertest = ReturnType; + +export class ApmApiError extends Error { + res: ApiErrorResponse; + + constructor(res: request.Response, endpoint: string) { + super( + `Unhandled ApmApiError. +Status: "${res.status}" +Endpoint: "${endpoint}" +Body: ${JSON.stringify(res.body)}` + ); + + this.res = res; + } +} + +export interface SupertestReturnType { + status: number; + body: APIReturnType; +} + +export function ApmApiProvider(context: DeploymentAgnosticFtrProviderContext) { + return { + readUser: createApmApiClient(context, 'viewer'), + adminUser: createApmApiClient(context, 'admin'), + writeUser: createApmApiClient(context, 'editor'), + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/services/index.ts b/x-pack/test/api_integration/deployment_agnostic/services/index.ts index 2b51bb1902dd1..01dc52571ee5a 100644 --- a/x-pack/test/api_integration/deployment_agnostic/services/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/services/index.ts @@ -13,6 +13,7 @@ import { PackageApiProvider } from './package_api'; import { RoleScopedSupertestProvider, SupertestWithRoleScope } from './role_scoped_supertest'; import { SloApiProvider } from './slo_api'; import { SynthtraceProvider } from './synthtrace'; +import { ApmApiProvider } from './apm_api'; export type { InternalRequestHeader, @@ -31,6 +32,7 @@ export const services = { roleScopedSupertest: RoleScopedSupertestProvider, // create a new deployment-agnostic service and load here synthtrace: SynthtraceProvider, + apmApi: ApmApiProvider, }; export type SupertestWithRoleScopeType = SupertestWithRoleScope; diff --git a/x-pack/test/api_integration/deployment_agnostic/services/synthtrace.ts b/x-pack/test/api_integration/deployment_agnostic/services/synthtrace.ts index 1ab2692095ca3..eda492ff37a43 100644 --- a/x-pack/test/api_integration/deployment_agnostic/services/synthtrace.ts +++ b/x-pack/test/api_integration/deployment_agnostic/services/synthtrace.ts @@ -39,7 +39,8 @@ export function SynthtraceProvider({ getService }: DeploymentAgnosticFtrProvider return { apmSynthtraceKibanaClient, createLogsSynthtraceEsClient: () => getLogsSynthtraceEsClient(client), - async createApmSynthtraceEsClient(packageVersion: string) { + async createApmSynthtraceEsClient() { + const packageVersion = (await apmSynthtraceKibanaClient.installApmPackage()).version; return getApmSynthtraceEsClient({ client, packageVersion }); }, }; diff --git a/x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts b/x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts deleted file mode 100644 index 00e3fedf4620c..0000000000000 --- a/x-pack/test/apm_api_integration/tests/agent_explorer/latest_agent_versions.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { ElasticApmAgentLatestVersion } from '@kbn/apm-plugin/common/agent_explorer'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - - const nodeAgentName = 'nodejs'; - const unlistedAgentName = 'unlistedAgent'; - - async function callApi() { - return await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/get_latest_agent_versions', - }); - } - - registry.when( - 'Agent latest versions when configuration is defined', - { config: 'basic', archives: [] }, - () => { - it('returns a version when agent is listed in the file', async () => { - const { status, body } = await callApi(); - expect(status).to.be(200); - - const agents = body.data; - - const nodeAgent = agents[nodeAgentName] as ElasticApmAgentLatestVersion; - expect(nodeAgent?.latest_version).not.to.be(undefined); - }); - - it('returns undefined when agent is not listed in the file', async () => { - const { status, body } = await callApi(); - expect(status).to.be(200); - - const agents = body.data; - - // @ts-ignore - const unlistedAgent = agents[unlistedAgentName] as ElasticApmAgentLatestVersion; - expect(unlistedAgent?.latest_version).to.be(undefined); - }); - } - ); -} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 2ba14ceb1218c..9db41aecbb612 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -187,6 +187,6 @@ "@kbn/alerting-types", "@kbn/ai-assistant-common", "@kbn/core-deprecations-common", - "@kbn/usage-collection-plugin" + "@kbn/usage-collection-plugin", ] } From 1b9079f16416cb0e18eb05ef9939520f7a80f767 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 6 Nov 2024 18:38:42 +0200 Subject: [PATCH 25/53] [Search: Home page] Remove the extra `.` from the `{number} active API keys.` text. (#199096) This PR fixes a small typo in https://github.com/elastic/kibana/pull/197456. No need to add `.` for text placed in `EuiBadge` ## Screen image --- .../public/applications/shared/api_key/api_key_panel.tsx | 2 +- .../shared/getting_started/panels/api_key_panel_content.tsx | 2 +- x-pack/plugins/translations/translations/fr-FR.json | 2 +- x-pack/plugins/translations/translations/ja-JP.json | 2 +- x-pack/plugins/translations/translations/zh-CN.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/api_key_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/api_key_panel.tsx index b9d5cf8c414d6..2a99b60f3745f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/api_key/api_key_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/api_key/api_key_panel.tsx @@ -141,7 +141,7 @@ export const ApiKeyPanel: React.FC = () => { > = ({ apiKeys, open > Date: Wed, 6 Nov 2024 16:47:02 +0000 Subject: [PATCH 26/53] skip flaky suite (#187932) --- .../cypress/e2e/response_actions/response_console/scan.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts index 04630647ed35f..543961ef9900b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts @@ -41,7 +41,8 @@ describe( login(); }); - describe('Scan operation:', () => { + // FLAKY: https://github.com/elastic/kibana/issues/187932 + describe.skip('Scan operation:', () => { const homeFilePath = Cypress.env('IS_CI') ? '/home/vagrant' : '/home'; const fileContent = 'This is a test file for the scan command.'; From bd6cf31c4397be76d10443b23ce15988a717ef55 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 6 Nov 2024 16:49:19 +0000 Subject: [PATCH 27/53] skip flaky suite (#192739) --- .../cases/public/components/all_cases/all_cases_list.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index b0c57d0cc3dd5..bc540040cce57 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -92,7 +92,8 @@ const mockKibana = () => { } as unknown as ReturnType); }; -describe('AllCasesListGeneric', () => { +// FLAKY: https://github.com/elastic/kibana/issues/192739 +describe.skip('AllCasesListGeneric', () => { const onRowClick = jest.fn(); const updateCaseProperty = jest.fn(); From f410085ffc6e37f284ff72b034ca177f536f7121 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:55:33 +0000 Subject: [PATCH 28/53] [Observability] Fix integrations detected twice (#198670) Resolves [#195912](https://github.com/elastic/kibana/issues/195912) ## Summary Fixes an issue where integrations were displayed twice in the list of detected log files. Co-authored-by: Joe Reuter --- .../public/assets/auto_detect.sh | 30 ++++++++++++++----- .../public/assets/integrations.conf | 2 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/assets/auto_detect.sh b/x-pack/plugins/observability_solution/observability_onboarding/public/assets/auto_detect.sh index 3b45ed3baf626..a77f24790f9d3 100755 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/assets/auto_detect.sh +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/assets/auto_detect.sh @@ -325,15 +325,31 @@ apply_elastic_agent_config() { read_open_log_file_list() { local exclude_patterns=( - "^\/Users\/.+?\/Library\/Application Support" - "^\/Users\/.+?\/Library\/Group Containers" - "^\/Users\/.+?\/Library\/Containers" - "^\/Users\/.+?\/Library\/Caches" - "^\/private" + "^\/Users\/.+?\/Library\/Application Support\/" + "^\/Users\/.+?\/Library\/Group Containers\/" + "^\/Users\/.+?\/Library\/Containers\/" + "^\/Users\/.+?\/Library\/Caches\/" + "^\/private\/" + + # Integrations only ingest a subset of application logs so there are scenarios where additional + # log files could be detected and displayed as a "custom log" alongside the detected integration + # they belong to. To avoid this UX issue we exclude all log files inside application directories + # from the custom log file detection + "^\/var\/log\/nginx\/" + "^\/var\/log\/apache2\/" + "^\/var\/log\/httpd\/" + "^\/var\/log\/mysql\/" + "^\/var\/log\/postgresql\/" + "^\/var\/log\/redis\/" + "^\/var\/log\/rabbitmq\/" + "^\/var\/log\/kafka\/" + "^\/var\/log\/mongodb\/" + "^\/opt\/tomcat\/logs\/" + "^\/var\/log\/prometheus\/" # Exclude previous installation logs - "\/opt\/Elastic\/Agent\/" - "\/Library\/Elastic\/Agent\/" + "^\/opt\/Elastic\/Agent\/" + "^\/Library\/Elastic\/Agent\/" ) # Excluding all patterns that correspond to known integrations diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/assets/integrations.conf b/x-pack/plugins/observability_solution/observability_onboarding/public/assets/integrations.conf index 0b197bef30f7d..48ad18035edc0 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/assets/integrations.conf +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/assets/integrations.conf @@ -34,9 +34,9 @@ patterns= title=PostgreSQL patterns= /var/log/postgresql/postgresql-*-*.log* + /var/log/postgresql/postgresql-*-*.csv* /*/postgresql-logs/*.log /etc/postgresql/*/main/postgresql.conf - /var/log/postgresql/postgresql-*-*.csv* [redis] title=Redis From cc66320e970443cede6b9c9a4ab67fb16062e1a4 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 6 Nov 2024 18:25:24 +0100 Subject: [PATCH 29/53] [SecuritySolution][SIEM migrations] Implement background task API (#197997) ## Summary It implements the background task to execute the rule migrations and the API to manage them. It also contains a basic implementation of the langGraph agent workflow that will perform the migration using generative AI. > [!NOTE] > This feature needs `siemMigrationsEnabled` experimental flag enabled to work. Otherwise, the new API routes won't be registered, and the `SiemRuleMigrationsService` _setup_ won't be called. So no migration task code can be reached, and no data stream/template will be installed to ES. ### The rule migration task implementation: - Retrieve a batch of N rule migration documents (50 rules initially, we may change that later) with `status: pending`. - Update those documents to `status: processing`. - Execute the migration for each of the N migrations in parallel. - If there is any error update the document with `status: error`. - For each rule migration that finishes we set the result to the storage, and also update `status: finished`. - When all the batch of rules is finished the task will check if there are still migration documents with `status: pending` if so it will process the next batch with a delay (10 seconds initially, we may change that later). - If the task is stopped (via API call or server shut-down), we do a bulk update for all the `status: processing` documents back to `status: pending`. ### Task API - `POST /internal/siem_migrations/rules` (implemented [here](https://github.com/elastic/security-team/issues/10654)) -> Creates the migration on the backend and stores the original rules. It returns the `migration_id` - `GET /internal/siem_migrations/rules/stats` -> Retrieves the stats for all the existing migrations, aggregated by `migration_id`. - `GET /internal/siem_migrations/rules/{migration_id}` -> Retrieves all the migration rule documents of a specific migration. - `PUT /internal/siem_migrations/rules/{migration_id}/start` -> Starts the background task for a specific migration. - `GET /internal/siem_migrations/rules/{migration_id}/stats` -> Retrieves the stats of a specific migration task. The UI will do polling to this endpoint. - `PUT /internal/siem_migrations/rules/{migration_id}/stop` -> Stops the execution of a specific migration running task. When a migration is stopped, the executing task is aborted and all the rules in the batch being processed are moved back to pending, all finished rules will remain stored. When the Kibana server shuts down all the running migrations are stopped automatically. To resume the migration we can call `{migration_id}/start` again and it will take it from the same rules batch it was left. #### Stats (UI polling) response example: ``` { "status": "running", "rules": { "total": 34, "finished": 20, "pending": 4, "processing": 10, "failed": 0 }, "last_updated_at": "2024-10-29T15:04:49.618Z" } ``` ### LLM agent Graph The initial implementation of the agent graph that is executed per rule: ![agent graph diagram](https://github.com/user-attachments/assets/9228350c-a469-449b-a58a-0b452bb805aa) The first node tries to match the original rule with an Elastic prebuilt rule. If it does not succeed, the second node will try to translate the query as a custom rule using the ES|QL knowledge base, this composes previous PoCs: - https://github.com/elastic/kibana/pull/193900 - https://github.com/elastic/kibana/pull/196651 ## Testing locally Enable the flag ``` xpack.securitySolution.enableExperimental: ['siemMigrationsEnabled'] ``` cURL request examples:
Rules migration `create` POST request ``` curl --location --request POST 'http://elastic:changeme@localhost:5601/internal/siem_migrations/rules' \ --header 'kbn-xsrf;' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'elastic-api-version: 1' \ --header 'Content-Type: application/json' \ --data '[ { "id": "f8c325ea-506e-4105-8ccf-da1492e90115", "vendor": "splunk", "title": "Linux Auditd Add User Account Type", "description": "The following analytic detects the suspicious add user account type. This behavior is critical for a SOC to monitor because it may indicate attempts to gain unauthorized access or maintain control over a system. Such actions could be signs of malicious activity. If confirmed, this could lead to serious consequences, including a compromised system, unauthorized access to sensitive data, or even a wider breach affecting the entire network. Detecting and responding to these signs early is essential to prevent potential security incidents.", "query": "sourcetype=\"linux:audit\" type=ADD_USER \n| rename hostname as dest \n| stats count min(_time) as firstTime max(_time) as lastTime by exe pid dest res UID type \n| `security_content_ctime(firstTime)` \n| `security_content_ctime(lastTime)`\n| search *", "query_language":"spl", "mitre_attack_ids": [ "T1136" ] }, { "id": "7b87c556-0ca4-47e0-b84c-6cd62a0a3e90", "vendor": "splunk", "title": "Linux Auditd Change File Owner To Root", "description": "The following analytic detects the use of the '\''chown'\'' command to change a file owner to '\''root'\'' on a Linux system. It leverages Linux Auditd telemetry, specifically monitoring command-line executions and process details. This activity is significant as it may indicate an attempt to escalate privileges by adversaries, malware, or red teamers. If confirmed malicious, this action could allow an attacker to gain root-level access, leading to full control over the compromised host and potential persistence within the environment.", "query": "`linux_auditd` `linux_auditd_normalized_proctitle_process`\r\n| rename host as dest \r\n| where LIKE (process_exec, \"%chown %root%\") \r\n| stats count min(_time) as firstTime max(_time) as lastTime by process_exec proctitle normalized_proctitle_delimiter dest \r\n| `security_content_ctime(firstTime)` \r\n| `security_content_ctime(lastTime)`\r\n| `linux_auditd_change_file_owner_to_root_filter`", "query_language": "spl", "mitre_attack_ids": [ "T1222" ] } ]' ```
Rules migration `start` task request - Assuming the connector `azureOpenAiGPT4o` is already created in the local environment. - Using the {{`migration_id`}} from the first POST request response ``` curl --location --request PUT 'http://elastic:changeme@localhost:5601/internal/siem_migrations/rules/{{migration_id}}/start' \ --header 'kbn-xsrf;' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'elastic-api-version: 1' \ --header 'Content-Type: application/json' \ --data '{ "connectorId": "azureOpenAiGPT4o" }' ```
Rules migration `stop` task request - Using the {{`migration_id`}} from the first POST request response. ``` curl --location --request PUT 'http://elastic:changeme@localhost:5601/internal/siem_migrations/rules/{{migration_id}}/stop' \ --header 'kbn-xsrf;' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'elastic-api-version: 1' ```
Rules migration task `stats` request - Using the {{`migration_id`}} from the first POST request response. ``` curl --location --request GET 'http://elastic:changeme@localhost:5601/internal/siem_migrations/rules/{{migration_id}}/stats' \ --header 'kbn-xsrf;' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'elastic-api-version: 1' ```
Rules migration rules documents request - Using the {{`migration_id`}} from the first POST request response. ``` curl --location --request GET 'http://elastic:changeme@localhost:5601/internal/siem_migrations/rules/{{migration_id}}' \ --header 'kbn-xsrf;' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'elastic-api-version: 1' ```
Rules migration all stats request ``` curl --location --request GET 'http://elastic:changeme@localhost:5601/internal/siem_migrations/rules/stats' \ --header 'kbn-xsrf;' \ --header 'x-elastic-internal-origin: security-solution' \ --header 'elastic-api-version: 1' ```
--------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/api/quickstart_client.gen.ts | 89 +++++- .../common/siem_migrations/constants.ts | 21 +- .../model/api/rules/rules_migration.gen.ts | 63 +++- .../api/rules/rules_migration.schema.yaml | 139 ++++++++- .../model/rule_migration.gen.ts | 68 ++++- .../model/rule_migration.schema.yaml | 80 ++++- x-pack/plugins/security_solution/kibana.jsonc | 3 +- .../routes/__mocks__/request_context.ts | 6 +- .../lib/siem_migrations/__mocks__/mocks.ts | 8 +- .../__mocks__/siem_migrations_service.ts | 9 + .../siem_migrations/rules/__mocks__/mocks.ts | 51 +++- .../__mocks__/siem_rule_migrations_client.ts | 9 + .../lib/siem_migrations/rules/api/create.ts | 30 +- .../lib/siem_migrations/rules/api/get.ts | 47 +++ .../lib/siem_migrations/rules/api/index.ts | 10 + .../lib/siem_migrations/rules/api/start.ts | 91 ++++++ .../lib/siem_migrations/rules/api/stats.ts | 47 +++ .../siem_migrations/rules/api/stats_all.ts | 39 +++ .../lib/siem_migrations/rules/api/stop.ts | 50 +++ .../rules/data_stream/__mocks__/mocks.ts | 4 +- .../rule_migrations_data_client.ts | 275 +++++++++++++++++ .../rule_migrations_data_stream.test.ts | 57 ++-- .../rule_migrations_data_stream.ts | 44 ++- .../data_stream/rule_migrations_field_map.ts | 3 +- .../siem_rule_migrations_service.test.ts | 72 ++--- .../rules/siem_rule_migrations_service.ts | 59 ++-- .../siem_migrations/rules/task/agent/graph.ts | 43 +++ .../siem_migrations/rules/task/agent/index.ts | 8 + .../agent/nodes/match_prebuilt_rule/index.ts | 8 + .../match_prebuilt_rule.ts | 59 ++++ .../nodes/match_prebuilt_rule/prompts.ts | 35 +++ .../esql_knowledge_base_caller.ts | 36 +++ .../task/agent/nodes/translate_query/index.ts | 7 + .../agent/nodes/translate_query/prompt.ts | 39 +++ .../nodes/translate_query/translate_query.ts | 56 ++++ .../siem_migrations/rules/task/agent/state.ts | 32 ++ .../siem_migrations/rules/task/agent/types.ts | 23 ++ .../rules/task/rule_migrations_task_runner.ts | 285 ++++++++++++++++++ .../lib/siem_migrations/rules/task/types.ts | 70 +++++ .../rules/task/util/actions_client_chat.ts | 93 ++++++ .../rules/task/util/prebuilt_rules.test.ts | 105 +++++++ .../rules/task/util/prebuilt_rules.ts | 77 +++++ .../server/lib/siem_migrations/rules/types.ts | 48 ++- .../siem_migrations_service.test.ts | 22 +- .../siem_migrations_service.ts | 14 +- .../server/lib/siem_migrations/types.ts | 8 +- .../server/plugin_contract.ts | 2 + .../server/request_context_factory.ts | 10 +- .../plugins/security_solution/server/types.ts | 6 +- .../plugins/security_solution/tsconfig.json | 1 + .../services/security_solution_api.gen.ts | 87 +++++- 51 files changed, 2346 insertions(+), 202 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/siem_migrations_service.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/siem_rule_migrations_client.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats_all.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stop.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_client.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/esql_knowledge_base_caller.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompt.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_runner.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/types.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/actions_client_chat.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index e4f4faaa7a2d5..8ad3dfbf451c7 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -365,7 +365,16 @@ import type { import type { CreateRuleMigrationRequestBodyInput, CreateRuleMigrationResponse, + GetAllStatsRuleMigrationResponse, + GetRuleMigrationRequestParamsInput, GetRuleMigrationResponse, + GetRuleMigrationStatsRequestParamsInput, + GetRuleMigrationStatsResponse, + StartRuleMigrationRequestParamsInput, + StartRuleMigrationRequestBodyInput, + StartRuleMigrationResponse, + StopRuleMigrationRequestParamsInput, + StopRuleMigrationResponse, } from '../siem_migrations/model/api/rules/rules_migration.gen'; export interface ClientOptions { @@ -1238,6 +1247,21 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Retrieves the rule migrations stats for all migrations stored in the system + */ + async getAllStatsRuleMigration() { + this.log.info(`${new Date().toISOString()} Calling API GetAllStatsRuleMigration`); + return this.kbnClient + .request({ + path: '/internal/siem_migrations/rules/stats', + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + }, + method: 'GET', + }) + .catch(catchAxiosErrorFormatAndThrow); + } /** * Get the asset criticality record for a specific entity. */ @@ -1431,13 +1455,28 @@ finalize it. .catch(catchAxiosErrorFormatAndThrow); } /** - * Retrieves the rule migrations stored in the system + * Retrieves the rule documents stored in the system given the rule migration id */ - async getRuleMigration() { + async getRuleMigration(props: GetRuleMigrationProps) { this.log.info(`${new Date().toISOString()} Calling API GetRuleMigration`); return this.kbnClient .request({ - path: '/internal/siem_migrations/rules', + path: replaceParams('/internal/siem_migrations/rules/{migration_id}', props.params), + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + }, + method: 'GET', + }) + .catch(catchAxiosErrorFormatAndThrow); + } + /** + * Retrieves the stats of a SIEM rules migration using the migration id provided + */ + async getRuleMigrationStats(props: GetRuleMigrationStatsProps) { + this.log.info(`${new Date().toISOString()} Calling API GetRuleMigrationStats`); + return this.kbnClient + .request({ + path: replaceParams('/internal/siem_migrations/rules/{migration_id}/stats', props.params), headers: { [ELASTIC_HTTP_VERSION_HEADER]: '1', }, @@ -1973,6 +2012,22 @@ detection engine rules. }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Starts a SIEM rules migration using the migration id provided + */ + async startRuleMigration(props: StartRuleMigrationProps) { + this.log.info(`${new Date().toISOString()} Calling API StartRuleMigration`); + return this.kbnClient + .request({ + path: replaceParams('/internal/siem_migrations/rules/{migration_id}/start', props.params), + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + }, + method: 'PUT', + body: props.body, + }) + .catch(catchAxiosErrorFormatAndThrow); + } async stopEntityEngine(props: StopEntityEngineProps) { this.log.info(`${new Date().toISOString()} Calling API StopEntityEngine`); return this.kbnClient @@ -1985,6 +2040,21 @@ detection engine rules. }) .catch(catchAxiosErrorFormatAndThrow); } + /** + * Stops a running SIEM rules migration using the migration id provided + */ + async stopRuleMigration(props: StopRuleMigrationProps) { + this.log.info(`${new Date().toISOString()} Calling API StopRuleMigration`); + return this.kbnClient + .request({ + path: replaceParams('/internal/siem_migrations/rules/{migration_id}/stop', props.params), + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '1', + }, + method: 'PUT', + }) + .catch(catchAxiosErrorFormatAndThrow); + } /** * Suggests user profiles. */ @@ -2221,6 +2291,12 @@ export interface GetRuleExecutionResultsProps { query: GetRuleExecutionResultsRequestQueryInput; params: GetRuleExecutionResultsRequestParamsInput; } +export interface GetRuleMigrationProps { + params: GetRuleMigrationRequestParamsInput; +} +export interface GetRuleMigrationStatsProps { + params: GetRuleMigrationStatsRequestParamsInput; +} export interface GetTimelineProps { query: GetTimelineRequestQueryInput; } @@ -2297,9 +2373,16 @@ export interface SetAlertTagsProps { export interface StartEntityEngineProps { params: StartEntityEngineRequestParamsInput; } +export interface StartRuleMigrationProps { + params: StartRuleMigrationRequestParamsInput; + body: StartRuleMigrationRequestBodyInput; +} export interface StopEntityEngineProps { params: StopEntityEngineRequestParamsInput; } +export interface StopRuleMigrationProps { + params: StopRuleMigrationRequestParamsInput; +} export interface SuggestUserProfilesProps { query: SuggestUserProfilesRequestQueryInput; } diff --git a/x-pack/plugins/security_solution/common/siem_migrations/constants.ts b/x-pack/plugins/security_solution/common/siem_migrations/constants.ts index 96ca75679f112..f2efc646a8101 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/constants.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/constants.ts @@ -8,9 +8,24 @@ export const SIEM_MIGRATIONS_PATH = '/internal/siem_migrations' as const; export const SIEM_RULE_MIGRATIONS_PATH = `${SIEM_MIGRATIONS_PATH}/rules` as const; -export enum SiemMigrationsStatus { +export const SIEM_RULE_MIGRATIONS_ALL_STATS_PATH = `${SIEM_RULE_MIGRATIONS_PATH}/stats` as const; +export const SIEM_RULE_MIGRATIONS_GET_PATH = `${SIEM_RULE_MIGRATIONS_PATH}/{migration_id}` as const; +export const SIEM_RULE_MIGRATIONS_START_PATH = + `${SIEM_RULE_MIGRATIONS_PATH}/{migration_id}/start` as const; +export const SIEM_RULE_MIGRATIONS_STATS_PATH = + `${SIEM_RULE_MIGRATIONS_PATH}/{migration_id}/stats` as const; +export const SIEM_RULE_MIGRATIONS_STOP_PATH = + `${SIEM_RULE_MIGRATIONS_PATH}/{migration_id}/stop` as const; + +export enum SiemMigrationStatus { PENDING = 'pending', PROCESSING = 'processing', - FINISHED = 'finished', - ERROR = 'error', + COMPLETED = 'completed', + FAILED = 'failed', +} + +export enum SiemMigrationRuleTranslationResult { + FULL = 'full', + PARTIAL = 'partial', + UNTRANSLATABLE = 'untranslatable', } diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.gen.ts index fa8a1cc8a6778..120505ec43cb7 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.gen.ts @@ -16,7 +16,13 @@ import { z } from '@kbn/zod'; -import { OriginalRule, RuleMigration } from '../../rule_migration.gen'; +import { + OriginalRule, + RuleMigrationAllTaskStats, + RuleMigration, + RuleMigrationTaskStats, +} from '../../rule_migration.gen'; +import { ConnectorId, LangSmithOptions } from '../common.gen'; export type CreateRuleMigrationRequestBody = z.infer; export const CreateRuleMigrationRequestBody = z.array(OriginalRule); @@ -30,5 +36,60 @@ export const CreateRuleMigrationResponse = z.object({ migration_id: z.string(), }); +export type GetAllStatsRuleMigrationResponse = z.infer; +export const GetAllStatsRuleMigrationResponse = RuleMigrationAllTaskStats; + +export type GetRuleMigrationRequestParams = z.infer; +export const GetRuleMigrationRequestParams = z.object({ + migration_id: z.string(), +}); +export type GetRuleMigrationRequestParamsInput = z.input; + export type GetRuleMigrationResponse = z.infer; export const GetRuleMigrationResponse = z.array(RuleMigration); + +export type GetRuleMigrationStatsRequestParams = z.infer; +export const GetRuleMigrationStatsRequestParams = z.object({ + migration_id: z.string(), +}); +export type GetRuleMigrationStatsRequestParamsInput = z.input< + typeof GetRuleMigrationStatsRequestParams +>; + +export type GetRuleMigrationStatsResponse = z.infer; +export const GetRuleMigrationStatsResponse = RuleMigrationTaskStats; + +export type StartRuleMigrationRequestParams = z.infer; +export const StartRuleMigrationRequestParams = z.object({ + migration_id: z.string(), +}); +export type StartRuleMigrationRequestParamsInput = z.input; + +export type StartRuleMigrationRequestBody = z.infer; +export const StartRuleMigrationRequestBody = z.object({ + connector_id: ConnectorId, + langsmith_options: LangSmithOptions.optional(), +}); +export type StartRuleMigrationRequestBodyInput = z.input; + +export type StartRuleMigrationResponse = z.infer; +export const StartRuleMigrationResponse = z.object({ + /** + * Indicates the migration has been started. `false` means the migration does not need to be started. + */ + started: z.boolean(), +}); + +export type StopRuleMigrationRequestParams = z.infer; +export const StopRuleMigrationRequestParams = z.object({ + migration_id: z.string(), +}); +export type StopRuleMigrationRequestParamsInput = z.input; + +export type StopRuleMigrationResponse = z.infer; +export const StopRuleMigrationResponse = z.object({ + /** + * Indicates the migration has been stopped. + */ + stopped: z.boolean(), +}); diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.schema.yaml index 40596ba7e712d..7b06c3d6a22ac 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rules_migration.schema.yaml @@ -10,8 +10,7 @@ paths: x-codegen-enabled: true description: Creates a new SIEM rules migration using the original vendor rules provided tags: - - SIEM Migrations - - Rule Migrations + - SIEM Rule Migrations requestBody: required: true content: @@ -33,20 +32,146 @@ paths: migration_id: type: string description: The migration id created. + + /internal/siem_migrations/rules/stats: get: - summary: Retrieves rule migrations - operationId: GetRuleMigration + summary: Retrieves the stats for all rule migrations + operationId: GetAllStatsRuleMigration x-codegen-enabled: true - description: Retrieves the rule migrations stored in the system + description: Retrieves the rule migrations stats for all migrations stored in the system tags: - - SIEM Migrations - - Rule Migrations + - SIEM Rule Migrations responses: 200: description: Indicates rule migrations have been retrieved correctly. + content: + application/json: + schema: + $ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigrationAllTaskStats' + + /internal/siem_migrations/rules/{migration_id}: + get: + summary: Retrieves all the rules of a migration + operationId: GetRuleMigration + x-codegen-enabled: true + description: Retrieves the rule documents stored in the system given the rule migration id + tags: + - SIEM Rule Migrations + parameters: + - name: migration_id + in: path + required: true + schema: + type: string + description: The migration id to start + responses: + 200: + description: Indicates rule migration have been retrieved correctly. content: application/json: schema: type: array items: $ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigration' + 204: + description: Indicates the migration id was not found. + + /internal/siem_migrations/rules/{migration_id}/start: + put: + summary: Starts a rule migration + operationId: StartRuleMigration + x-codegen-enabled: true + description: Starts a SIEM rules migration using the migration id provided + tags: + - SIEM Rule Migrations + parameters: + - name: migration_id + in: path + required: true + schema: + type: string + description: The migration id to start + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - connector_id + properties: + connector_id: + $ref: '../common.schema.yaml#/components/schemas/ConnectorId' + langsmith_options: + $ref: '../common.schema.yaml#/components/schemas/LangSmithOptions' + responses: + 200: + description: Indicates the migration start request has been processed successfully. + content: + application/json: + schema: + type: object + required: + - started + properties: + started: + type: boolean + description: Indicates the migration has been started. `false` means the migration does not need to be started. + 204: + description: Indicates the migration id was not found. + + /internal/siem_migrations/rules/{migration_id}/stats: + get: + summary: Gets a rule migration task stats + operationId: GetRuleMigrationStats + x-codegen-enabled: true + description: Retrieves the stats of a SIEM rules migration using the migration id provided + tags: + - SIEM Rule Migrations + parameters: + - name: migration_id + in: path + required: true + schema: + type: string + description: The migration id to start + responses: + 200: + description: Indicates the migration stats has been retrieved correctly. + content: + application/json: + schema: + $ref: '../../rule_migration.schema.yaml#/components/schemas/RuleMigrationTaskStats' + 204: + description: Indicates the migration id was not found. + + /internal/siem_migrations/rules/{migration_id}/stop: + put: + summary: Stops an existing rule migration + operationId: StopRuleMigration + x-codegen-enabled: true + description: Stops a running SIEM rules migration using the migration id provided + tags: + - SIEM Rule Migrations + parameters: + - name: migration_id + in: path + required: true + schema: + type: string + description: The migration id to stop + responses: + 200: + description: Indicates migration task stop has been processed successfully. + content: + application/json: + schema: + type: object + required: + - stopped + properties: + stopped: + type: boolean + description: Indicates the migration has been stopped. + 204: + description: Indicates the migration id was not found running. diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts index 0e07ef2f208da..fe00c4b4df1c6 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts @@ -71,11 +71,11 @@ export const ElasticRule = z.object({ /** * The translated elastic query. */ - query: z.string(), + query: z.string().optional(), /** * The translated elastic query language. */ - query_language: z.literal('esql').default('esql'), + query_language: z.literal('esql').optional(), /** * The Elastic prebuilt rule id matched. */ @@ -99,16 +99,20 @@ export const RuleMigration = z.object({ * The migration id. */ migration_id: z.string(), + /** + * The username of the user who created the migration. + */ + created_by: z.string(), original_rule: OriginalRule, elastic_rule: ElasticRule.optional(), /** - * The translation state. + * The rule translation result. */ - translation_state: z.enum(['complete', 'partial', 'untranslatable']).optional(), + translation_result: z.enum(['full', 'partial', 'untranslatable']).optional(), /** - * The status of the rule migration. + * The status of the rule migration process. */ - status: z.enum(['pending', 'processing', 'finished', 'error']).default('pending'), + status: z.enum(['pending', 'processing', 'completed', 'failed']).default('pending'), /** * The comments for the migration including a summary from the LLM in markdown. */ @@ -122,3 +126,55 @@ export const RuleMigration = z.object({ */ updated_by: z.string().optional(), }); + +/** + * The rule migration task stats object. + */ +export type RuleMigrationTaskStats = z.infer; +export const RuleMigrationTaskStats = z.object({ + /** + * Indicates if the migration task status. + */ + status: z.enum(['ready', 'running', 'stopped', 'finished']), + /** + * The rules migration stats. + */ + rules: z.object({ + /** + * The total number of rules to migrate. + */ + total: z.number().int(), + /** + * The number of rules that are pending migration. + */ + pending: z.number().int(), + /** + * The number of rules that are being migrated. + */ + processing: z.number().int(), + /** + * The number of rules that have been migrated successfully. + */ + completed: z.number().int(), + /** + * The number of rules that have failed migration. + */ + failed: z.number().int(), + }), + /** + * The moment of the last update. + */ + last_updated_at: z.string().optional(), +}); + +export type RuleMigrationAllTaskStats = z.infer; +export const RuleMigrationAllTaskStats = z.array( + RuleMigrationTaskStats.merge( + z.object({ + /** + * The migration id + */ + migration_id: z.string(), + }) + ) +); diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml index 9ec825389a52b..c9841856a6914 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml @@ -48,8 +48,6 @@ components: description: The migrated elastic rule. required: - title - - query - - query_language properties: title: type: string @@ -68,7 +66,6 @@ components: description: The translated elastic query language. enum: - esql - default: esql prebuilt_rule_id: type: string description: The Elastic prebuilt rule id matched. @@ -84,32 +81,36 @@ components: - migration_id - original_rule - status + - created_by properties: - "@timestamp": + '@timestamp': type: string description: The moment of creation migration_id: type: string description: The migration id. + created_by: + type: string + description: The username of the user who created the migration. original_rule: $ref: '#/components/schemas/OriginalRule' elastic_rule: $ref: '#/components/schemas/ElasticRule' - translation_state: + translation_result: type: string - description: The translation state. - enum: - - complete + description: The rule translation result. + enum: # should match SiemMigrationRuleTranslationResult enum at ../constants.ts + - full - partial - untranslatable status: type: string - description: The status of the rule migration. + description: The status of the rule migration process. enum: # should match SiemMigrationsStatus enum at ../constants.ts - pending - processing - - finished - - error + - completed + - failed default: pending comments: type: array @@ -122,3 +123,60 @@ components: updated_by: type: string description: The user who last updated the migration + + RuleMigrationTaskStats: + type: object + description: The rule migration task stats object. + required: + - status + - rules + properties: + status: + type: string + description: Indicates if the migration task status. + enum: + - ready + - running + - stopped + - finished + rules: + type: object + description: The rules migration stats. + required: + - total + - pending + - processing + - completed + - failed + properties: + total: + type: integer + description: The total number of rules to migrate. + pending: + type: integer + description: The number of rules that are pending migration. + processing: + type: integer + description: The number of rules that are being migrated. + completed: + type: integer + description: The number of rules that have been migrated successfully. + failed: + type: integer + description: The number of rules that have failed migration. + last_updated_at: + type: string + description: The moment of the last update. + + RuleMigrationAllTaskStats: + type: array + items: + allOf: + - $ref: '#/components/schemas/RuleMigrationTaskStats' + - type: object + required: + - migration_id + properties: + migration_id: + type: string + description: The migration id diff --git a/x-pack/plugins/security_solution/kibana.jsonc b/x-pack/plugins/security_solution/kibana.jsonc index d6f3e5f6580e3..0e713bc095888 100644 --- a/x-pack/plugins/security_solution/kibana.jsonc +++ b/x-pack/plugins/security_solution/kibana.jsonc @@ -58,7 +58,8 @@ "savedSearch", "unifiedDocViewer", "charts", - "entityManager" + "entityManager", + "inference" ], "optionalPlugins": [ "encryptedSavedObjects", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index 5e47428cd1749..cba989890438e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -79,7 +79,8 @@ export const createMockClients = () => { internalFleetServices: { packages: packageServiceMock.createClient(), }, - siemMigrationsClient: siemMigrationsServiceMock.createClient(), + siemRuleMigrationsClient: siemMigrationsServiceMock.createRulesClient(), + getInferenceClient: jest.fn(), }; }; @@ -166,7 +167,8 @@ const createSecuritySolutionRequestContextMock = ( getAuditLogger: jest.fn(() => mockAuditLogger), getDataViewsService: jest.fn(), getEntityStoreDataClient: jest.fn(() => clients.entityStoreDataClient), - getSiemMigrationsClient: jest.fn(() => clients.siemMigrationsClient), + getSiemRuleMigrationsClient: jest.fn(() => clients.siemRuleMigrationsClient), + getInferenceClient: jest.fn(() => clients.getInferenceClient()), }; }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/mocks.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/mocks.ts index fcf119e19ece5..af961d48db5b1 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/mocks.ts @@ -7,18 +7,16 @@ import { createRuleMigrationClient } from '../rules/__mocks__/mocks'; -const createClient = () => ({ rules: createRuleMigrationClient() }); - export const mockSetup = jest.fn().mockResolvedValue(undefined); -export const mockCreateClient = jest.fn().mockReturnValue(createClient()); +export const mockCreateClient = jest.fn().mockReturnValue(createRuleMigrationClient()); export const mockStop = jest.fn(); export const siemMigrationsServiceMock = { create: () => jest.fn().mockImplementation(() => ({ setup: mockSetup, - createClient: mockCreateClient, + createRulesClient: mockCreateClient, stop: mockStop, })), - createClient: () => createClient(), + createRulesClient: () => createRuleMigrationClient(), }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/siem_migrations_service.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/siem_migrations_service.ts new file mode 100644 index 0000000000000..659929d47570f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/__mocks__/siem_migrations_service.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { siemMigrationsServiceMock } from './mocks'; +export const SiemMigrationsService = siemMigrationsServiceMock.create(); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/mocks.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/mocks.ts index 8233151f513e4..8811a54195e2b 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/mocks.ts @@ -5,17 +5,56 @@ * 2.0. */ -import type { SiemRuleMigrationsClient } from '../types'; - -export const createRuleMigrationClient = (): SiemRuleMigrationsClient => ({ +export const createRuleMigrationDataClient = jest.fn().mockImplementation(() => ({ create: jest.fn().mockResolvedValue({ success: true }), - search: jest.fn().mockResolvedValue([]), + getRules: jest.fn().mockResolvedValue([]), + takePending: jest.fn().mockResolvedValue([]), + saveFinished: jest.fn().mockResolvedValue({ success: true }), + saveError: jest.fn().mockResolvedValue({ success: true }), + releaseProcessing: jest.fn().mockResolvedValue({ success: true }), + releaseProcessable: jest.fn().mockResolvedValue({ success: true }), + getStats: jest.fn().mockResolvedValue({ + status: 'done', + rules: { + total: 1, + finished: 1, + processing: 0, + pending: 0, + failed: 0, + }, + }), + getAllStats: jest.fn().mockResolvedValue([]), +})); + +export const createRuleMigrationTaskClient = () => ({ + start: jest.fn().mockResolvedValue({ started: true }), + stop: jest.fn().mockResolvedValue({ stopped: true }), + getStats: jest.fn().mockResolvedValue({ + status: 'done', + rules: { + total: 1, + finished: 1, + processing: 0, + pending: 0, + failed: 0, + }, + }), + getAllStats: jest.fn().mockResolvedValue([]), }); +export const createRuleMigrationClient = () => ({ + data: createRuleMigrationDataClient(), + task: createRuleMigrationTaskClient(), +}); + +export const MockSiemRuleMigrationsClient = jest.fn().mockImplementation(createRuleMigrationClient); + export const mockSetup = jest.fn(); -export const mockGetClient = jest.fn().mockReturnValue(createRuleMigrationClient()); +export const mockCreateClient = jest.fn().mockReturnValue(createRuleMigrationClient()); +export const mockStop = jest.fn(); export const MockSiemRuleMigrationsService = jest.fn().mockImplementation(() => ({ setup: mockSetup, - getClient: mockGetClient, + createClient: mockCreateClient, + stop: mockStop, })); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/siem_rule_migrations_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/siem_rule_migrations_client.ts new file mode 100644 index 0000000000000..98032605ed233 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/__mocks__/siem_rule_migrations_client.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MockSiemRuleMigrationsClient } from './mocks'; +export const SiemRuleMigrationsClient = MockSiemRuleMigrationsClient; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts index f4c52e9b444b8..e2505ca83beed 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts @@ -8,14 +8,11 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { v4 as uuidV4 } from 'uuid'; -import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import type { CreateRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; import { CreateRuleMigrationRequestBody } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; -import { - SIEM_RULE_MIGRATIONS_PATH, - SiemMigrationsStatus, -} from '../../../../../common/siem_migrations/constants'; +import { SIEM_RULE_MIGRATIONS_PATH } from '../../../../../common/siem_migrations/constants'; import type { SecuritySolutionPluginRouter } from '../../../../types'; +import type { CreateRuleMigrationInput } from '../data_stream/rule_migrations_data_client'; export const registerSiemRuleMigrationsCreateRoute = ( router: SecuritySolutionPluginRouter, @@ -25,11 +22,7 @@ export const registerSiemRuleMigrationsCreateRoute = ( .post({ path: SIEM_RULE_MIGRATIONS_PATH, access: 'internal', - security: { - authz: { - requiredPrivileges: ['securitySolution'], - }, - }, + security: { authz: { requiredPrivileges: ['securitySolution'] } }, }) .addVersion( { @@ -41,27 +34,22 @@ export const registerSiemRuleMigrationsCreateRoute = ( async (context, req, res): Promise> => { const originalRules = req.body; try { - const ctx = await context.resolve(['core', 'actions', 'securitySolution']); - - const siemMigrationClient = ctx.securitySolution.getSiemMigrationsClient(); + const ctx = await context.resolve(['securitySolution']); + const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); const migrationId = uuidV4(); - const timestamp = new Date().toISOString(); - const ruleMigrations = originalRules.map((originalRule) => ({ - '@timestamp': timestamp, + const ruleMigrations = originalRules.map((originalRule) => ({ migration_id: migrationId, original_rule: originalRule, - status: SiemMigrationsStatus.PENDING, })); - await siemMigrationClient.rules.create(ruleMigrations); + + await ruleMigrationsClient.data.create(ruleMigrations); return res.ok({ body: { migration_id: migrationId } }); } catch (err) { logger.error(err); - return res.badRequest({ - body: err.message, - }); + return res.badRequest({ body: err.message }); } } ); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts new file mode 100644 index 0000000000000..0efb6706918f5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { GetRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { GetRuleMigrationRequestParams } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { SIEM_RULE_MIGRATIONS_GET_PATH } from '../../../../../common/siem_migrations/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +export const registerSiemRuleMigrationsGetRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .get({ + path: SIEM_RULE_MIGRATIONS_GET_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { + version: '1', + validate: { + request: { params: buildRouteValidationWithZod(GetRuleMigrationRequestParams) }, + }, + }, + async (context, req, res): Promise> => { + const migrationId = req.params.migration_id; + try { + const ctx = await context.resolve(['securitySolution']); + const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); + + const migrationRules = await ruleMigrationsClient.data.getRules(migrationId); + + return res.ok({ body: migrationRules }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts index 0de49eb7df92b..f37eb2108a8a4 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/index.ts @@ -8,10 +8,20 @@ import type { Logger } from '@kbn/core/server'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { registerSiemRuleMigrationsCreateRoute } from './create'; +import { registerSiemRuleMigrationsGetRoute } from './get'; +import { registerSiemRuleMigrationsStartRoute } from './start'; +import { registerSiemRuleMigrationsStatsRoute } from './stats'; +import { registerSiemRuleMigrationsStopRoute } from './stop'; +import { registerSiemRuleMigrationsStatsAllRoute } from './stats_all'; export const registerSiemRuleMigrationsRoutes = ( router: SecuritySolutionPluginRouter, logger: Logger ) => { registerSiemRuleMigrationsCreateRoute(router, logger); + registerSiemRuleMigrationsStatsAllRoute(router, logger); + registerSiemRuleMigrationsGetRoute(router, logger); + registerSiemRuleMigrationsStartRoute(router, logger); + registerSiemRuleMigrationsStatsRoute(router, logger); + registerSiemRuleMigrationsStopRoute(router, logger); }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts new file mode 100644 index 0000000000000..f97a4f2ce2398 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { APMTracer } from '@kbn/langchain/server/tracers/apm'; +import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import type { StartRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { + StartRuleMigrationRequestBody, + StartRuleMigrationRequestParams, +} from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { SIEM_RULE_MIGRATIONS_START_PATH } from '../../../../../common/siem_migrations/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +export const registerSiemRuleMigrationsStartRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .put({ + path: SIEM_RULE_MIGRATIONS_START_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: buildRouteValidationWithZod(StartRuleMigrationRequestParams), + body: buildRouteValidationWithZod(StartRuleMigrationRequestBody), + }, + }, + }, + async (context, req, res): Promise> => { + const migrationId = req.params.migration_id; + const { langsmith_options: langsmithOptions, connector_id: connectorId } = req.body; + + try { + const ctx = await context.resolve([ + 'core', + 'actions', + 'alerting', + 'securitySolution', + 'licensing', + ]); + if (!ctx.licensing.license.hasAtLeast('enterprise')) { + return res.forbidden({ + body: 'You must have a trial or enterprise license to use this feature', + }); + } + + const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); + const inferenceClient = ctx.securitySolution.getInferenceClient(); + const actionsClient = ctx.actions.getActionsClient(); + const soClient = ctx.core.savedObjects.client; + const rulesClient = ctx.alerting.getRulesClient(); + + const invocationConfig = { + callbacks: [ + new APMTracer({ projectName: langsmithOptions?.project_name ?? 'default' }, logger), + ...getLangSmithTracer({ ...langsmithOptions, logger }), + ], + }; + + const { exists, started } = await ruleMigrationsClient.task.start({ + migrationId, + connectorId, + invocationConfig, + inferenceClient, + actionsClient, + soClient, + rulesClient, + }); + + if (!exists) { + return res.noContent(); + } + return res.ok({ body: { started } }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats.ts new file mode 100644 index 0000000000000..8316e01fc6a9b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { GetRuleMigrationStatsResponse } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { GetRuleMigrationStatsRequestParams } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { SIEM_RULE_MIGRATIONS_STATS_PATH } from '../../../../../common/siem_migrations/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +export const registerSiemRuleMigrationsStatsRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .get({ + path: SIEM_RULE_MIGRATIONS_STATS_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { + version: '1', + validate: { + request: { params: buildRouteValidationWithZod(GetRuleMigrationStatsRequestParams) }, + }, + }, + async (context, req, res): Promise> => { + const migrationId = req.params.migration_id; + try { + const ctx = await context.resolve(['securitySolution']); + const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); + + const stats = await ruleMigrationsClient.task.getStats(migrationId); + + return res.ok({ body: stats }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats_all.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats_all.ts new file mode 100644 index 0000000000000..dd2f2f503e19d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stats_all.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, Logger } from '@kbn/core/server'; +import type { GetAllStatsRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { SIEM_RULE_MIGRATIONS_ALL_STATS_PATH } from '../../../../../common/siem_migrations/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +export const registerSiemRuleMigrationsStatsAllRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .get({ + path: SIEM_RULE_MIGRATIONS_ALL_STATS_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { version: '1', validate: {} }, + async (context, req, res): Promise> => { + try { + const ctx = await context.resolve(['securitySolution']); + const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); + + const allStats = await ruleMigrationsClient.task.getAllStats(); + + return res.ok({ body: allStats }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stop.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stop.ts new file mode 100644 index 0000000000000..4767106910186 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/stop.ts @@ -0,0 +1,50 @@ +/* + * 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 { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { StopRuleMigrationResponse } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { StopRuleMigrationRequestParams } from '../../../../../common/siem_migrations/model/api/rules/rules_migration.gen'; +import { SIEM_RULE_MIGRATIONS_STOP_PATH } from '../../../../../common/siem_migrations/constants'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +export const registerSiemRuleMigrationsStopRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + router.versioned + .put({ + path: SIEM_RULE_MIGRATIONS_STOP_PATH, + access: 'internal', + security: { authz: { requiredPrivileges: ['securitySolution'] } }, + }) + .addVersion( + { + version: '1', + validate: { + request: { params: buildRouteValidationWithZod(StopRuleMigrationRequestParams) }, + }, + }, + async (context, req, res): Promise> => { + const migrationId = req.params.migration_id; + try { + const ctx = await context.resolve(['securitySolution']); + const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); + + const { exists, stopped } = await ruleMigrationsClient.task.stop(migrationId); + + if (!exists) { + return res.noContent(); + } + return res.ok({ body: { stopped } }); + } catch (err) { + logger.error(err); + return res.badRequest({ body: err.message }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/__mocks__/mocks.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/__mocks__/mocks.ts index 103c0f9b0c952..1d9a181d2de5b 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/__mocks__/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/__mocks__/mocks.ts @@ -7,9 +7,9 @@ export const mockIndexName = 'mocked_data_stream_name'; export const mockInstall = jest.fn().mockResolvedValue(undefined); -export const mockInstallSpace = jest.fn().mockResolvedValue(mockIndexName); +export const mockCreateClient = jest.fn().mockReturnValue({}); export const MockRuleMigrationsDataStream = jest.fn().mockImplementation(() => ({ install: mockInstall, - installSpace: mockInstallSpace, + createClient: mockCreateClient, })); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_client.ts new file mode 100644 index 0000000000000..83808901a0bd1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_client.ts @@ -0,0 +1,275 @@ +/* + * 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 { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; +import assert from 'assert'; +import type { + AggregationsFilterAggregate, + AggregationsMaxAggregate, + AggregationsStringTermsAggregate, + AggregationsStringTermsBucket, + QueryDslQueryContainer, + SearchHit, + SearchResponse, +} from '@elastic/elasticsearch/lib/api/types'; +import type { StoredRuleMigration } from '../types'; +import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants'; +import type { + RuleMigration, + RuleMigrationTaskStats, +} from '../../../../../common/siem_migrations/model/rule_migration.gen'; + +export type CreateRuleMigrationInput = Omit; +export type RuleMigrationDataStats = Omit; +export type RuleMigrationAllDataStats = Array; + +export class RuleMigrationsDataClient { + constructor( + private dataStreamNamePromise: Promise, + private currentUser: AuthenticatedUser, + private esClient: ElasticsearchClient, + private logger: Logger + ) {} + + /** Indexes an array of rule migrations to be processed */ + async create(ruleMigrations: CreateRuleMigrationInput[]): Promise { + const index = await this.dataStreamNamePromise; + await this.esClient + .bulk({ + refresh: 'wait_for', + operations: ruleMigrations.flatMap((ruleMigration) => [ + { create: { _index: index } }, + { + ...ruleMigration, + '@timestamp': new Date().toISOString(), + status: SiemMigrationStatus.PENDING, + created_by: this.currentUser.username, + }, + ]), + }) + .catch((error) => { + this.logger.error(`Error creating rule migrations: ${error.message}`); + throw error; + }); + } + + /** Retrieves an array of rule documents of a specific migrations */ + async getRules(migrationId: string): Promise { + const index = await this.dataStreamNamePromise; + const query = this.getFilterQuery(migrationId); + + const storedRuleMigrations = await this.esClient + .search({ index, query, sort: '_doc' }) + .catch((error) => { + this.logger.error(`Error searching getting rule migrations: ${error.message}`); + throw error; + }) + .then((response) => this.processHits(response.hits.hits)); + return storedRuleMigrations; + } + + /** + * Retrieves `pending` rule migrations with the provided id and updates their status to `processing`. + * This operation is not atomic at migration level: + * - Multiple tasks can process different migrations simultaneously. + * - Multiple tasks should not process the same migration simultaneously. + */ + async takePending(migrationId: string, size: number): Promise { + const index = await this.dataStreamNamePromise; + const query = this.getFilterQuery(migrationId, SiemMigrationStatus.PENDING); + + const storedRuleMigrations = await this.esClient + .search({ index, query, sort: '_doc', size }) + .catch((error) => { + this.logger.error(`Error searching for rule migrations: ${error.message}`); + throw error; + }) + .then((response) => + this.processHits(response.hits.hits, { status: SiemMigrationStatus.PROCESSING }) + ); + + await this.esClient + .bulk({ + refresh: 'wait_for', + operations: storedRuleMigrations.flatMap(({ _id, _index, status }) => [ + { update: { _id, _index } }, + { + doc: { + status, + updated_by: this.currentUser.username, + updated_at: new Date().toISOString(), + }, + }, + ]), + }) + .catch((error) => { + this.logger.error( + `Error updating for rule migrations status to processing: ${error.message}` + ); + throw error; + }); + + return storedRuleMigrations; + } + + /** Updates one rule migration with the provided data and sets the status to `completed` */ + async saveFinished({ _id, _index, ...ruleMigration }: StoredRuleMigration): Promise { + const doc = { + ...ruleMigration, + status: SiemMigrationStatus.COMPLETED, + updated_by: this.currentUser.username, + updated_at: new Date().toISOString(), + }; + await this.esClient + .update({ index: _index, id: _id, doc, refresh: 'wait_for' }) + .catch((error) => { + this.logger.error(`Error updating rule migration status to completed: ${error.message}`); + throw error; + }); + } + + /** Updates one rule migration with the provided data and sets the status to `failed` */ + async saveError({ _id, _index, ...ruleMigration }: StoredRuleMigration): Promise { + const doc = { + ...ruleMigration, + status: SiemMigrationStatus.FAILED, + updated_by: this.currentUser.username, + updated_at: new Date().toISOString(), + }; + await this.esClient + .update({ index: _index, id: _id, doc, refresh: 'wait_for' }) + .catch((error) => { + this.logger.error(`Error updating rule migration status to completed: ${error.message}`); + throw error; + }); + } + + /** Updates all the rule migration with the provided id with status `processing` back to `pending` */ + async releaseProcessing(migrationId: string): Promise { + const index = await this.dataStreamNamePromise; + const query = this.getFilterQuery(migrationId, SiemMigrationStatus.PROCESSING); + const script = { source: `ctx._source['status'] = '${SiemMigrationStatus.PENDING}'` }; + await this.esClient.updateByQuery({ index, query, script, refresh: false }).catch((error) => { + this.logger.error(`Error releasing rule migrations status to pending: ${error.message}`); + throw error; + }); + } + + /** Updates all the rule migration with the provided id with status `processing` or `failed` back to `pending` */ + async releaseProcessable(migrationId: string): Promise { + const index = await this.dataStreamNamePromise; + const query = this.getFilterQuery(migrationId, [ + SiemMigrationStatus.PROCESSING, + SiemMigrationStatus.FAILED, + ]); + const script = { source: `ctx._source['status'] = '${SiemMigrationStatus.PENDING}'` }; + await this.esClient.updateByQuery({ index, query, script, refresh: true }).catch((error) => { + this.logger.error(`Error releasing rule migrations status to pending: ${error.message}`); + throw error; + }); + } + + /** Retrieves the stats for the rule migrations with the provided id */ + async getStats(migrationId: string): Promise { + const index = await this.dataStreamNamePromise; + const query = this.getFilterQuery(migrationId); + const aggregations = { + pending: { filter: { term: { status: SiemMigrationStatus.PENDING } } }, + processing: { filter: { term: { status: SiemMigrationStatus.PROCESSING } } }, + completed: { filter: { term: { status: SiemMigrationStatus.COMPLETED } } }, + failed: { filter: { term: { status: SiemMigrationStatus.FAILED } } }, + lastUpdatedAt: { max: { field: 'updated_at' } }, + }; + const result = await this.esClient + .search({ index, query, aggregations, _source: false }) + .catch((error) => { + this.logger.error(`Error getting rule migrations stats: ${error.message}`); + throw error; + }); + + const { pending, processing, completed, lastUpdatedAt, failed } = result.aggregations ?? {}; + return { + rules: { + total: this.getTotalHits(result), + pending: (pending as AggregationsFilterAggregate)?.doc_count ?? 0, + processing: (processing as AggregationsFilterAggregate)?.doc_count ?? 0, + completed: (completed as AggregationsFilterAggregate)?.doc_count ?? 0, + failed: (failed as AggregationsFilterAggregate)?.doc_count ?? 0, + }, + last_updated_at: (lastUpdatedAt as AggregationsMaxAggregate)?.value_as_string, + }; + } + + /** Retrieves the stats for all the rule migrations aggregated by migration id */ + async getAllStats(): Promise { + const index = await this.dataStreamNamePromise; + const aggregations = { + migrationIds: { + terms: { field: 'migration_id' }, + aggregations: { + pending: { filter: { term: { status: SiemMigrationStatus.PENDING } } }, + processing: { filter: { term: { status: SiemMigrationStatus.PROCESSING } } }, + completed: { filter: { term: { status: SiemMigrationStatus.COMPLETED } } }, + failed: { filter: { term: { status: SiemMigrationStatus.FAILED } } }, + lastUpdatedAt: { max: { field: 'updated_at' } }, + }, + }, + }; + const result = await this.esClient + .search({ index, aggregations, _source: false }) + .catch((error) => { + this.logger.error(`Error getting all rule migrations stats: ${error.message}`); + throw error; + }); + + const migrationsAgg = result.aggregations?.migrationIds as AggregationsStringTermsAggregate; + const buckets = (migrationsAgg?.buckets as AggregationsStringTermsBucket[]) ?? []; + return buckets.map((bucket) => ({ + migration_id: bucket.key, + rules: { + total: bucket.doc_count, + pending: bucket.pending?.doc_count ?? 0, + processing: bucket.processing?.doc_count ?? 0, + completed: bucket.completed?.doc_count ?? 0, + failed: bucket.failed?.doc_count ?? 0, + }, + last_updated_at: bucket.lastUpdatedAt?.value_as_string, + })); + } + + private getFilterQuery( + migrationId: string, + status?: SiemMigrationStatus | SiemMigrationStatus[] + ): QueryDslQueryContainer { + const filter: QueryDslQueryContainer[] = [{ term: { migration_id: migrationId } }]; + if (status) { + if (Array.isArray(status)) { + filter.push({ terms: { status } }); + } else { + filter.push({ term: { status } }); + } + } + return { bool: { filter } }; + } + + private processHits( + hits: Array>, + override: Partial = {} + ): StoredRuleMigration[] { + return hits.map(({ _id, _index, _source }) => { + assert(_id, 'RuleMigration document should have _id'); + assert(_source, 'RuleMigration document should have _source'); + return { ..._source, ...override, _id, _index }; + }); + } + + private getTotalHits(response: SearchResponse) { + return typeof response.hits.total === 'number' + ? response.hits.total + : response.hits.total?.value ?? 0; + } +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.test.ts index 56510da48f1bb..467d26a380945 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.test.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.test.ts @@ -11,9 +11,19 @@ import type { InstallParams } from '@kbn/data-stream-adapter'; import { DataStreamSpacesAdapter } from '@kbn/data-stream-adapter'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { securityServiceMock } from '@kbn/core-security-server-mocks'; jest.mock('@kbn/data-stream-adapter'); +// This mock is required to have a way to await the data stream name promise +const mockDataStreamNamePromise = jest.fn(); +jest.mock('./rule_migrations_data_client', () => ({ + RuleMigrationsDataClient: jest.fn((dataStreamNamePromise: Promise) => { + mockDataStreamNamePromise.mockReturnValue(dataStreamNamePromise); + }), +})); + const MockedDataStreamSpacesAdapter = DataStreamSpacesAdapter as unknown as jest.MockedClass< typeof DataStreamSpacesAdapter >; @@ -21,18 +31,21 @@ const MockedDataStreamSpacesAdapter = DataStreamSpacesAdapter as unknown as jest const esClient = elasticsearchServiceMock.createStart().client.asInternalUser; describe('SiemRuleMigrationsDataStream', () => { + const kibanaVersion = '8.16.0'; + const logger = loggingSystemMock.createLogger(); + beforeEach(() => { jest.clearAllMocks(); }); describe('constructor', () => { it('should create DataStreamSpacesAdapter', () => { - new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); + new RuleMigrationsDataStream(logger, kibanaVersion); expect(MockedDataStreamSpacesAdapter).toHaveBeenCalledTimes(1); }); it('should create component templates', () => { - new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); + new RuleMigrationsDataStream(logger, kibanaVersion); const [dataStreamSpacesAdapter] = MockedDataStreamSpacesAdapter.mock.instances; expect(dataStreamSpacesAdapter.setComponentTemplate).toHaveBeenCalledWith( expect.objectContaining({ name: '.kibana.siem-rule-migrations' }) @@ -40,7 +53,7 @@ describe('SiemRuleMigrationsDataStream', () => { }); it('should create index templates', () => { - new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); + new RuleMigrationsDataStream(logger, kibanaVersion); const [dataStreamSpacesAdapter] = MockedDataStreamSpacesAdapter.mock.instances; expect(dataStreamSpacesAdapter.setIndexTemplate).toHaveBeenCalledWith( expect.objectContaining({ name: '.kibana.siem-rule-migrations' }) @@ -50,22 +63,20 @@ describe('SiemRuleMigrationsDataStream', () => { describe('install', () => { it('should install data stream', async () => { - const dataStream = new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); - const params: InstallParams = { + const dataStream = new RuleMigrationsDataStream(logger, kibanaVersion); + const params: Omit = { esClient, - logger: loggerMock.create(), pluginStop$: new Subject(), }; await dataStream.install(params); const [dataStreamSpacesAdapter] = MockedDataStreamSpacesAdapter.mock.instances; - expect(dataStreamSpacesAdapter.install).toHaveBeenCalledWith(params); + expect(dataStreamSpacesAdapter.install).toHaveBeenCalledWith(expect.objectContaining(params)); }); it('should log error', async () => { - const dataStream = new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); - const params: InstallParams = { + const dataStream = new RuleMigrationsDataStream(logger, kibanaVersion); + const params: Omit = { esClient, - logger: loggerMock.create(), pluginStop$: new Subject(), }; const [dataStreamSpacesAdapter] = MockedDataStreamSpacesAdapter.mock.instances; @@ -73,13 +84,16 @@ describe('SiemRuleMigrationsDataStream', () => { (dataStreamSpacesAdapter.install as jest.Mock).mockRejectedValueOnce(error); await dataStream.install(params); - expect(params.logger.error).toHaveBeenCalledWith(expect.any(String), error); + expect(logger.error).toHaveBeenCalledWith(expect.any(String), error); }); }); - describe('installSpace', () => { + describe('createClient', () => { + const currentUser = securityServiceMock.createMockAuthenticatedUser(); + const createClientParams = { spaceId: 'space1', currentUser, esClient }; + it('should install space data stream', async () => { - const dataStream = new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); + const dataStream = new RuleMigrationsDataStream(logger, kibanaVersion); const params: InstallParams = { esClient, logger: loggerMock.create(), @@ -89,19 +103,23 @@ describe('SiemRuleMigrationsDataStream', () => { (dataStreamSpacesAdapter.install as jest.Mock).mockResolvedValueOnce(undefined); await dataStream.install(params); - await dataStream.installSpace('space1'); + dataStream.createClient(createClientParams); + await mockDataStreamNamePromise(); expect(dataStreamSpacesAdapter.getInstalledSpaceName).toHaveBeenCalledWith('space1'); expect(dataStreamSpacesAdapter.installSpace).toHaveBeenCalledWith('space1'); }); it('should not install space data stream if install not executed', async () => { - const dataStream = new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); - await expect(dataStream.installSpace('space1')).rejects.toThrowError(); + const dataStream = new RuleMigrationsDataStream(logger, kibanaVersion); + await expect(async () => { + dataStream.createClient(createClientParams); + await mockDataStreamNamePromise(); + }).rejects.toThrowError(); }); it('should throw error if main install had error', async () => { - const dataStream = new RuleMigrationsDataStream({ kibanaVersion: '8.13.0' }); + const dataStream = new RuleMigrationsDataStream(logger, kibanaVersion); const params: InstallParams = { esClient, logger: loggerMock.create(), @@ -112,7 +130,10 @@ describe('SiemRuleMigrationsDataStream', () => { (dataStreamSpacesAdapter.install as jest.Mock).mockRejectedValueOnce(error); await dataStream.install(params); - await expect(dataStream.installSpace('space1')).rejects.toThrowError(error); + await expect(async () => { + dataStream.createClient(createClientParams); + await mockDataStreamNamePromise(); + }).rejects.toThrowError(error); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.ts index 83eb471e0cee3..a5855cefb1324 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_data_stream.ts @@ -6,51 +6,69 @@ */ import { DataStreamSpacesAdapter, type InstallParams } from '@kbn/data-stream-adapter'; +import type { AuthenticatedUser, ElasticsearchClient, Logger } from '@kbn/core/server'; import { ruleMigrationsFieldMap } from './rule_migrations_field_map'; +import { RuleMigrationsDataClient } from './rule_migrations_data_client'; const TOTAL_FIELDS_LIMIT = 2500; const DATA_STREAM_NAME = '.kibana.siem-rule-migrations'; -const ECS_COMPONENT_TEMPLATE_NAME = 'ecs'; + +interface RuleMigrationsDataStreamCreateClientParams { + spaceId: string; + currentUser: AuthenticatedUser; + esClient: ElasticsearchClient; +} export class RuleMigrationsDataStream { - private readonly dataStream: DataStreamSpacesAdapter; + private readonly dataStreamAdapter: DataStreamSpacesAdapter; private installPromise?: Promise; - constructor({ kibanaVersion }: { kibanaVersion: string }) { - this.dataStream = new DataStreamSpacesAdapter(DATA_STREAM_NAME, { + constructor(private logger: Logger, kibanaVersion: string) { + this.dataStreamAdapter = new DataStreamSpacesAdapter(DATA_STREAM_NAME, { kibanaVersion, totalFieldsLimit: TOTAL_FIELDS_LIMIT, }); - this.dataStream.setComponentTemplate({ + this.dataStreamAdapter.setComponentTemplate({ name: DATA_STREAM_NAME, fieldMap: ruleMigrationsFieldMap, }); - this.dataStream.setIndexTemplate({ + this.dataStreamAdapter.setIndexTemplate({ name: DATA_STREAM_NAME, - componentTemplateRefs: [DATA_STREAM_NAME, ECS_COMPONENT_TEMPLATE_NAME], + componentTemplateRefs: [DATA_STREAM_NAME], }); } - async install(params: InstallParams) { + async install(params: Omit) { try { - this.installPromise = this.dataStream.install(params); + this.installPromise = this.dataStreamAdapter.install({ ...params, logger: this.logger }); await this.installPromise; } catch (err) { - params.logger.error(`Error installing siem rule migrations data stream. ${err.message}`, err); + this.logger.error(`Error installing siem rule migrations data stream. ${err.message}`, err); } } - async installSpace(spaceId: string): Promise { + createClient({ + spaceId, + currentUser, + esClient, + }: RuleMigrationsDataStreamCreateClientParams): RuleMigrationsDataClient { + const dataStreamNamePromise = this.installSpace(spaceId); + return new RuleMigrationsDataClient(dataStreamNamePromise, currentUser, esClient, this.logger); + } + + // Installs the data stream for the specific space. it will only install if it hasn't been installed yet. + // The adapter stores the data stream name promise, it will return it directly when the data stream is known to be installed. + private async installSpace(spaceId: string): Promise { if (!this.installPromise) { throw new Error('Siem rule migrations data stream not installed'); } // wait for install to complete, may reject if install failed, routes should handle this await this.installPromise; - let dataStreamName = await this.dataStream.getInstalledSpaceName(spaceId); + let dataStreamName = await this.dataStreamAdapter.getInstalledSpaceName(spaceId); if (!dataStreamName) { - dataStreamName = await this.dataStream.installSpace(spaceId); + dataStreamName = await this.dataStreamAdapter.installSpace(spaceId); } return dataStreamName; } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_field_map.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_field_map.ts index ba9a706957bcb..a65cd45b832e9 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_field_map.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data_stream/rule_migrations_field_map.ts @@ -11,6 +11,7 @@ import type { RuleMigration } from '../../../../../common/siem_migrations/model/ export const ruleMigrationsFieldMap: FieldMap> = { '@timestamp': { type: 'date', required: false }, migration_id: { type: 'keyword', required: true }, + created_by: { type: 'keyword', required: true }, status: { type: 'keyword', required: true }, original_rule: { type: 'nested', required: true }, 'original_rule.vendor': { type: 'keyword', required: true }, @@ -28,7 +29,7 @@ export const ruleMigrationsFieldMap: FieldMap> 'elastic_rule.severity': { type: 'keyword', required: false }, 'elastic_rule.prebuilt_rule_id': { type: 'keyword', required: false }, 'elastic_rule.id': { type: 'keyword', required: false }, - translation_state: { type: 'keyword', required: false }, + translation_result: { type: 'keyword', required: false }, comments: { type: 'text', array: true, required: false }, updated_at: { type: 'date', required: false }, updated_by: { type: 'keyword', required: false }, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.test.ts index 390d302264cea..5c611d85e0464 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.test.ts @@ -8,25 +8,28 @@ import { loggingSystemMock, elasticsearchServiceMock, httpServerMock, + securityServiceMock, } from '@kbn/core/server/mocks'; import { SiemRuleMigrationsService } from './siem_rule_migrations_service'; import { Subject } from 'rxjs'; -import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; import { MockRuleMigrationsDataStream, mockInstall, - mockInstallSpace, - mockIndexName, + mockCreateClient, } from './data_stream/__mocks__/mocks'; -import type { KibanaRequest } from '@kbn/core/server'; +import type { SiemRuleMigrationsCreateClientParams } from './types'; jest.mock('./data_stream/rule_migrations_data_stream'); +jest.mock('./task/rule_migrations_task_runner', () => ({ + RuleMigrationsTaskRunner: jest.fn(), +})); describe('SiemRuleMigrationsService', () => { let ruleMigrationsService: SiemRuleMigrationsService; const kibanaVersion = '8.16.0'; const esClusterClient = elasticsearchServiceMock.createClusterClient(); + const currentUser = securityServiceMock.createMockAuthenticatedUser(); const logger = loggingSystemMock.createLogger(); const pluginStop$ = new Subject(); @@ -36,7 +39,7 @@ describe('SiemRuleMigrationsService', () => { }); it('should instantiate the rule migrations data stream adapter', () => { - expect(MockRuleMigrationsDataStream).toHaveBeenCalledWith({ kibanaVersion }); + expect(MockRuleMigrationsDataStream).toHaveBeenCalledWith(logger, kibanaVersion); }); describe('when setup is called', () => { @@ -45,22 +48,26 @@ describe('SiemRuleMigrationsService', () => { expect(mockInstall).toHaveBeenCalledWith({ esClient: esClusterClient.asInternalUser, - logger, pluginStop$, }); }); }); - describe('when getClient is called', () => { - let request: KibanaRequest; + describe('when createClient is called', () => { + let createClientParams: SiemRuleMigrationsCreateClientParams; + beforeEach(() => { - request = httpServerMock.createKibanaRequest(); + createClientParams = { + spaceId: 'default', + currentUser, + request: httpServerMock.createKibanaRequest(), + }; }); describe('without setup', () => { it('should throw an error', () => { expect(() => { - ruleMigrationsService.getClient({ spaceId: 'default', request }); + ruleMigrationsService.createClient(createClientParams); }).toThrowError('ES client not available, please call setup first'); }); }); @@ -71,44 +78,19 @@ describe('SiemRuleMigrationsService', () => { }); it('should call installSpace', () => { - ruleMigrationsService.getClient({ spaceId: 'default', request }); - - expect(mockInstallSpace).toHaveBeenCalledWith('default'); - }); - - it('should return a client with create and search methods after setup', () => { - const client = ruleMigrationsService.getClient({ spaceId: 'default', request }); - - expect(client).toHaveProperty('create'); - expect(client).toHaveProperty('search'); + ruleMigrationsService.createClient(createClientParams); + expect(mockCreateClient).toHaveBeenCalledWith({ + spaceId: createClientParams.spaceId, + currentUser: createClientParams.currentUser, + esClient: esClusterClient.asScoped().asCurrentUser, + }); }); - it('should call ES bulk create API with the correct parameters with create is called', async () => { - const client = ruleMigrationsService.getClient({ spaceId: 'default', request }); - - const ruleMigrations = [{ migration_id: 'dummy_migration_id' } as RuleMigration]; - await client.create(ruleMigrations); - - expect(esClusterClient.asScoped().asCurrentUser.bulk).toHaveBeenCalledWith( - expect.objectContaining({ - body: [{ create: { _index: mockIndexName } }, { migration_id: 'dummy_migration_id' }], - refresh: 'wait_for', - }) - ); - }); - - it('should call ES search API with the correct parameters with search is called', async () => { - const client = ruleMigrationsService.getClient({ spaceId: 'default', request }); - - const term = { migration_id: 'dummy_migration_id' }; - await client.search(term); + it('should return data and task clients', () => { + const client = ruleMigrationsService.createClient(createClientParams); - expect(esClusterClient.asScoped().asCurrentUser.search).toHaveBeenCalledWith( - expect.objectContaining({ - index: mockIndexName, - body: { query: { term } }, - }) - ); + expect(client).toHaveProperty('data'); + expect(client).toHaveProperty('task'); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.ts index 5b20f957cb6fa..1bf9dcf11fd95 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/siem_rule_migrations_service.ts @@ -5,52 +5,67 @@ * 2.0. */ +import assert from 'assert'; import type { IClusterClient, Logger } from '@kbn/core/server'; import { RuleMigrationsDataStream } from './data_stream/rule_migrations_data_stream'; import type { - SiemRuleMigrationsClient, SiemRulesMigrationsSetupParams, - SiemRuleMigrationsGetClientParams, + SiemRuleMigrationsCreateClientParams, + SiemRuleMigrationsClient, } from './types'; +import { RuleMigrationsTaskRunner } from './task/rule_migrations_task_runner'; export class SiemRuleMigrationsService { - private dataStreamAdapter: RuleMigrationsDataStream; + private rulesDataStream: RuleMigrationsDataStream; private esClusterClient?: IClusterClient; + private taskRunner: RuleMigrationsTaskRunner; constructor(private logger: Logger, kibanaVersion: string) { - this.dataStreamAdapter = new RuleMigrationsDataStream({ kibanaVersion }); + this.rulesDataStream = new RuleMigrationsDataStream(this.logger, kibanaVersion); + this.taskRunner = new RuleMigrationsTaskRunner(this.logger); } setup({ esClusterClient, ...params }: SiemRulesMigrationsSetupParams) { this.esClusterClient = esClusterClient; const esClient = esClusterClient.asInternalUser; - this.dataStreamAdapter.install({ ...params, esClient, logger: this.logger }).catch((err) => { + + this.rulesDataStream.install({ ...params, esClient }).catch((err) => { this.logger.error(`Error installing data stream for rule migrations: ${err.message}`); throw err; }); } - getClient({ spaceId, request }: SiemRuleMigrationsGetClientParams): SiemRuleMigrationsClient { - if (!this.esClusterClient) { - throw new Error('ES client not available, please call setup first'); - } - // Installs the data stream for the specific space. it will only install if it hasn't been installed yet. - // The adapter stores the data stream name promise, it will return it directly when the data stream is known to be installed. - const dataStreamNamePromise = this.dataStreamAdapter.installSpace(spaceId); + createClient({ + spaceId, + currentUser, + request, + }: SiemRuleMigrationsCreateClientParams): SiemRuleMigrationsClient { + assert(currentUser, 'Current user must be authenticated'); + assert(this.esClusterClient, 'ES client not available, please call setup first'); const esClient = this.esClusterClient.asScoped(request).asCurrentUser; + const dataClient = this.rulesDataStream.createClient({ spaceId, currentUser, esClient }); + return { - create: async (ruleMigrations) => { - const _index = await dataStreamNamePromise; - return esClient.bulk({ - refresh: 'wait_for', - body: ruleMigrations.flatMap((ruleMigration) => [{ create: { _index } }, ruleMigration]), - }); - }, - search: async (term) => { - const index = await dataStreamNamePromise; - return esClient.search({ index, body: { query: { term } } }); + data: dataClient, + task: { + start: (params) => { + return this.taskRunner.start({ ...params, currentUser, dataClient }); + }, + stop: (migrationId) => { + return this.taskRunner.stop({ migrationId, dataClient }); + }, + getStats: async (migrationId) => { + return this.taskRunner.getStats({ migrationId, dataClient }); + }, + getAllStats: async () => { + return this.taskRunner.getAllStats({ dataClient }); + }, }, }; } + + stop() { + this.taskRunner.stopAll(); + } } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts new file mode 100644 index 0000000000000..a44197d82850f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { END, START, StateGraph } from '@langchain/langgraph'; +import { migrateRuleState } from './state'; +import type { MigrateRuleGraphParams, MigrateRuleState } from './types'; +import { getTranslateQueryNode } from './nodes/translate_query'; +import { getMatchPrebuiltRuleNode } from './nodes/match_prebuilt_rule'; + +export function getRuleMigrationAgent({ + model, + inferenceClient, + prebuiltRulesMap, + connectorId, + logger, +}: MigrateRuleGraphParams) { + const matchPrebuiltRuleNode = getMatchPrebuiltRuleNode({ model, prebuiltRulesMap, logger }); + const translationNode = getTranslateQueryNode({ inferenceClient, connectorId, logger }); + + const translateRuleGraph = new StateGraph(migrateRuleState) + // Nodes + .addNode('matchPrebuiltRule', matchPrebuiltRuleNode) + .addNode('translation', translationNode) + // Edges + .addEdge(START, 'matchPrebuiltRule') + .addConditionalEdges('matchPrebuiltRule', matchedPrebuiltRuleConditional) + .addEdge('translation', END); + + const graph = translateRuleGraph.compile(); + graph.name = 'Rule Migration Graph'; // Customizes the name displayed in LangSmith + return graph; +} + +const matchedPrebuiltRuleConditional = (state: MigrateRuleState) => { + if (state.elastic_rule?.prebuilt_rule_id) { + return END; + } + return 'translation'; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/index.ts new file mode 100644 index 0000000000000..febf5fc85f5a0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getRuleMigrationAgent } from './graph'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/index.ts new file mode 100644 index 0000000000000..2d8b81d00eafb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getMatchPrebuiltRuleNode } from './match_prebuilt_rule'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts new file mode 100644 index 0000000000000..4a0404acf653d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import { StringOutputParser } from '@langchain/core/output_parsers'; +import type { ChatModel } from '../../../util/actions_client_chat'; +import type { GraphNode } from '../../types'; +import { filterPrebuiltRules, type PrebuiltRulesMapByName } from '../../../util/prebuilt_rules'; +import { MATCH_PREBUILT_RULE_PROMPT } from './prompts'; + +interface GetMatchPrebuiltRuleNodeParams { + model: ChatModel; + prebuiltRulesMap: PrebuiltRulesMapByName; + logger: Logger; +} + +export const getMatchPrebuiltRuleNode = + ({ model, prebuiltRulesMap }: GetMatchPrebuiltRuleNodeParams): GraphNode => + async (state) => { + const mitreAttackIds = state.original_rule.mitre_attack_ids; + if (!mitreAttackIds?.length) { + return {}; + } + const filteredPrebuiltRulesMap = filterPrebuiltRules(prebuiltRulesMap, mitreAttackIds); + if (filteredPrebuiltRulesMap.size === 0) { + return {}; + } + + const outputParser = new StringOutputParser(); + const matchPrebuiltRule = MATCH_PREBUILT_RULE_PROMPT.pipe(model).pipe(outputParser); + + const elasticSecurityRules = Array(filteredPrebuiltRulesMap.keys()).join('\n'); + const response = await matchPrebuiltRule.invoke({ + elasticSecurityRules, + ruleTitle: state.original_rule.title, + }); + const cleanResponse = response.trim(); + if (cleanResponse === 'no_match') { + return {}; + } + + const result = filteredPrebuiltRulesMap.get(cleanResponse); + if (result != null) { + return { + elastic_rule: { + title: result.rule.name, + description: result.rule.description, + prebuilt_rule_id: result.rule.rule_id, + id: result.installedRuleId, + }, + }; + } + + return {}; + }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts new file mode 100644 index 0000000000000..434636d0519b1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ChatPromptTemplate } from '@langchain/core/prompts'; +export const MATCH_PREBUILT_RULE_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are an expert assistant in Cybersecurity, your task is to help migrating a SIEM detection rule, from Splunk Security to Elastic Security. +You will be provided with a Splunk Detection Rule name by the user, your goal is to try find an Elastic Detection Rule that covers the same threat, if any. +The list of Elastic Detection Rules suggested is provided in the context below. + +Guidelines: +If there is no Elastic rule in the list that covers the same threat, answer only with the string: no_match +If there is one Elastic rule in the list that covers the same threat, answer only with its name without any further explanation. +If there are multiple rules in the list that cover the same threat, answer with the most specific of them, for example: "Linux User Account Creation" is more specific than "User Account Creation". + + +{elasticSecurityRules} + +`, + ], + [ + 'human', + `The Splunk Detection Rule is: +<> +{ruleTitle} +<> +`, + ], + ['ai', 'Please find the answer below:'], +]); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/esql_knowledge_base_caller.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/esql_knowledge_base_caller.ts new file mode 100644 index 0000000000000..2277f2fae41a9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/esql_knowledge_base_caller.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import { naturalLanguageToEsql, type InferenceClient } from '@kbn/inference-plugin/server'; +import { lastValueFrom } from 'rxjs'; + +export type EsqlKnowledgeBaseCaller = (input: string) => Promise; + +type GetEsqlTranslatorToolParams = (params: { + inferenceClient: InferenceClient; + connectorId: string; + logger: Logger; +}) => EsqlKnowledgeBaseCaller; + +export const getEsqlKnowledgeBase: GetEsqlTranslatorToolParams = + ({ inferenceClient: client, connectorId, logger }) => + async (input: string) => { + const { content } = await lastValueFrom( + naturalLanguageToEsql({ + client, + connectorId, + input, + logger: { + debug: (source) => { + logger.debug(typeof source === 'function' ? source() : source); + }, + }, + }) + ); + return content; + }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts new file mode 100644 index 0000000000000..7d247f755e9da --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { getTranslateQueryNode } from './translate_query'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompt.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompt.ts new file mode 100644 index 0000000000000..0b97faf7dc96f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/prompt.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { MigrateRuleState } from '../../types'; + +export const getEsqlTranslationPrompt = ( + state: MigrateRuleState +): string => `You are a helpful cybersecurity (SIEM) expert agent. Your task is to migrate "detection rules" from Splunk to Elastic Security. +Below you will find Splunk rule information: the title, description and the SPL (Search Processing Language) query. +Your goal is to translate the SPL query into an equivalent Elastic Security Query Language (ES|QL) query. + +Guidelines: +- Start the translation process by analyzing the SPL query and identifying the key components. +- Always use logs* index pattern for the ES|QL translated query. +- If, in the SPL query, you find a lookup list or macro that, based only on its name, you can not translate with confidence to ES|QL, mention it in the summary and +add a placeholder in the query with the format [macro:(parameters)] or [lookup:] including the [] keys, example: [macro:my_macro(first_param,second_param)] or [lookup:my_lookup]. + +The output will be parsed and should contain: +- First, the ES|QL query inside an \`\`\`esql code block. +- At the end, the summary of the translation process followed in markdown, starting with "## Migration Summary". + +This is the Splunk rule information: + +<> +${state.original_rule.title} +<> + +<> +${state.original_rule.description} +<> + +<> +${state.original_rule.query} +<> +`; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts new file mode 100644 index 0000000000000..00e1e60c7b5f3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/translate_query/translate_query.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; +import type { GraphNode } from '../../types'; +import { getEsqlKnowledgeBase } from './esql_knowledge_base_caller'; +import { getEsqlTranslationPrompt } from './prompt'; +import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; + +interface GetTranslateQueryNodeParams { + inferenceClient: InferenceClient; + connectorId: string; + logger: Logger; +} + +export const getTranslateQueryNode = ({ + inferenceClient, + connectorId, + logger, +}: GetTranslateQueryNodeParams): GraphNode => { + const esqlKnowledgeBaseCaller = getEsqlKnowledgeBase({ inferenceClient, connectorId, logger }); + return async (state) => { + const input = getEsqlTranslationPrompt(state); + const response = await esqlKnowledgeBaseCaller(input); + + const esqlQuery = response.match(/```esql\n([\s\S]*?)\n```/)?.[1] ?? ''; + const summary = response.match(/## Migration Summary[\s\S]*$/)?.[0] ?? ''; + + const translationResult = getTranslationResult(esqlQuery); + + return { + response, + comments: [summary], + translation_result: translationResult, + elastic_rule: { + title: state.original_rule.title, + description: state.original_rule.description, + severity: 'low', + query: esqlQuery, + query_language: 'esql', + }, + }; + }; +}; + +const getTranslationResult = (esqlQuery: string): SiemMigrationRuleTranslationResult => { + if (esqlQuery.match(/\[(macro|lookup):[\s\S]*\]/)) { + return SiemMigrationRuleTranslationResult.PARTIAL; + } + return SiemMigrationRuleTranslationResult.FULL; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts new file mode 100644 index 0000000000000..c1e510bdc052d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { BaseMessage } from '@langchain/core/messages'; +import { Annotation, messagesStateReducer } from '@langchain/langgraph'; +import type { + ElasticRule, + OriginalRule, + RuleMigration, +} from '../../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { SiemMigrationRuleTranslationResult } from '../../../../../../common/siem_migrations/constants'; + +export const migrateRuleState = Annotation.Root({ + messages: Annotation({ + reducer: messagesStateReducer, + default: () => [], + }), + original_rule: Annotation(), + elastic_rule: Annotation({ + reducer: (state, action) => ({ ...state, ...action }), + }), + translation_result: Annotation(), + comments: Annotation({ + reducer: (current, value) => (value ? (current ?? []).concat(value) : current), + default: () => [], + }), + response: Annotation(), +}); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts new file mode 100644 index 0000000000000..643d200e4b0bf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; +import type { migrateRuleState } from './state'; +import type { ChatModel } from '../util/actions_client_chat'; +import type { PrebuiltRulesMapByName } from '../util/prebuilt_rules'; + +export type MigrateRuleState = typeof migrateRuleState.State; +export type GraphNode = (state: MigrateRuleState) => Promise>; + +export interface MigrateRuleGraphParams { + inferenceClient: InferenceClient; + model: ChatModel; + connectorId: string; + prebuiltRulesMap: PrebuiltRulesMapByName; + logger: Logger; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_runner.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_runner.ts new file mode 100644 index 0000000000000..6ae7294fb5257 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_runner.ts @@ -0,0 +1,285 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { AbortError, abortSignalToPromise } from '@kbn/kibana-utils-plugin/server'; +import type { RunnableConfig } from '@langchain/core/runnables'; +import type { + RuleMigrationAllTaskStats, + RuleMigrationTaskStats, +} from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { RuleMigrationDataStats } from '../data_stream/rule_migrations_data_client'; +import type { + RuleMigrationTaskStartParams, + RuleMigrationTaskStartResult, + RuleMigrationTaskStatsParams, + RuleMigrationTaskStopParams, + RuleMigrationTaskStopResult, + RuleMigrationTaskPrepareParams, + RuleMigrationTaskRunParams, + MigrationAgent, + RuleMigrationAllTaskStatsParams, +} from './types'; +import { getRuleMigrationAgent } from './agent'; +import type { MigrateRuleState } from './agent/types'; +import { retrievePrebuiltRulesMap } from './util/prebuilt_rules'; +import { ActionsClientChat } from './util/actions_client_chat'; + +interface TaskLogger { + info: (msg: string) => void; + debug: (msg: string) => void; + error: (msg: string, error: Error) => void; +} +const getTaskLogger = (logger: Logger): TaskLogger => { + const prefix = '[ruleMigrationsTask]: '; + return { + info: (msg) => logger.info(`${prefix}${msg}`), + debug: (msg) => logger.debug(`${prefix}${msg}`), + error: (msg, error) => logger.error(`${prefix}${msg}: ${error.message}`), + }; +}; + +const ITERATION_BATCH_SIZE = 50 as const; +const ITERATION_SLEEP_SECONDS = 10 as const; + +export class RuleMigrationsTaskRunner { + private migrationsRunning: Map; + private taskLogger: TaskLogger; + + constructor(private logger: Logger) { + this.migrationsRunning = new Map(); + this.taskLogger = getTaskLogger(logger); + } + + /** Starts a rule migration task */ + async start(params: RuleMigrationTaskStartParams): Promise { + const { migrationId, dataClient } = params; + if (this.migrationsRunning.has(migrationId)) { + return { exists: true, started: false }; + } + // Just in case some previous execution was interrupted without releasing + await dataClient.releaseProcessable(migrationId); + + const { rules } = await dataClient.getStats(migrationId); + if (rules.total === 0) { + return { exists: false, started: false }; + } + if (rules.pending === 0) { + return { exists: true, started: false }; + } + + const abortController = new AbortController(); + + // Await the preparation to make sure the agent is created properly so the task can run + const agent = await this.prepare({ ...params, abortController }); + + // not awaiting the `run` promise to execute the task in the background + this.run({ ...params, agent, abortController }).catch((err) => { + // All errors in the `run` method are already catch, this should never happen, but just in case + this.taskLogger.error(`Unexpected error running the migration ID:${migrationId}`, err); + }); + + return { exists: true, started: true }; + } + + private async prepare({ + connectorId, + inferenceClient, + actionsClient, + rulesClient, + soClient, + abortController, + }: RuleMigrationTaskPrepareParams): Promise { + const prebuiltRulesMap = await retrievePrebuiltRulesMap({ soClient, rulesClient }); + + const actionsClientChat = new ActionsClientChat(connectorId, actionsClient, this.logger); + const model = await actionsClientChat.createModel({ + signal: abortController.signal, + temperature: 0.05, + }); + + const agent = getRuleMigrationAgent({ + connectorId, + model, + inferenceClient, + prebuiltRulesMap, + logger: this.logger, + }); + return agent; + } + + private async run({ + migrationId, + agent, + dataClient, + currentUser, + invocationConfig, + abortController, + }: RuleMigrationTaskRunParams): Promise { + if (this.migrationsRunning.has(migrationId)) { + // This should never happen, but just in case + throw new Error(`Task already running for migration ID:${migrationId} `); + } + this.taskLogger.info(`Starting migration ID:${migrationId}`); + + this.migrationsRunning.set(migrationId, { user: currentUser.username, abortController }); + const config: RunnableConfig = { + ...invocationConfig, + // signal: abortController.signal, // not working properly https://github.com/langchain-ai/langgraphjs/issues/319 + }; + + const abortPromise = abortSignalToPromise(abortController.signal); + + try { + const sleep = async (seconds: number) => { + this.taskLogger.debug(`Sleeping ${seconds}s for migration ID:${migrationId}`); + await Promise.race([ + new Promise((resolve) => setTimeout(resolve, seconds * 1000)), + abortPromise.promise, + ]); + }; + + let isDone: boolean = false; + do { + const ruleMigrations = await dataClient.takePending(migrationId, ITERATION_BATCH_SIZE); + this.taskLogger.debug( + `Processing ${ruleMigrations.length} rules for migration ID:${migrationId}` + ); + + await Promise.all( + ruleMigrations.map(async (ruleMigration) => { + this.taskLogger.debug( + `Starting migration of rule "${ruleMigration.original_rule.title}"` + ); + try { + const start = Date.now(); + + const ruleMigrationResult: MigrateRuleState = await Promise.race([ + agent.invoke({ original_rule: ruleMigration.original_rule }, config), + abortPromise.promise, // workaround for the issue with the langGraph signal + ]); + + const duration = (Date.now() - start) / 1000; + this.taskLogger.debug( + `Migration of rule "${ruleMigration.original_rule.title}" finished in ${duration}s` + ); + + await dataClient.saveFinished({ + ...ruleMigration, + elastic_rule: ruleMigrationResult.elastic_rule, + translation_result: ruleMigrationResult.translation_result, + comments: ruleMigrationResult.comments, + }); + } catch (error) { + if (error instanceof AbortError) { + throw error; + } + this.taskLogger.error( + `Error migrating rule "${ruleMigration.original_rule.title}"`, + error + ); + await dataClient.saveError({ + ...ruleMigration, + comments: [`Error migrating rule: ${error.message}`], + }); + } + }) + ); + + this.taskLogger.debug(`Batch processed successfully for migration ID:${migrationId}`); + + const { rules } = await dataClient.getStats(migrationId); + isDone = rules.pending === 0; + if (!isDone) { + await sleep(ITERATION_SLEEP_SECONDS); + } + } while (!isDone); + + this.taskLogger.info(`Finished migration ID:${migrationId}`); + } catch (error) { + await dataClient.releaseProcessing(migrationId); + + if (error instanceof AbortError) { + this.taskLogger.info(`Abort signal received, stopping migration ID:${migrationId}`); + return; + } else { + this.taskLogger.error(`Error processing migration ID:${migrationId}`, error); + } + } finally { + this.migrationsRunning.delete(migrationId); + abortPromise.cleanup(); + } + } + + /** Returns the stats of a migration */ + async getStats({ + migrationId, + dataClient, + }: RuleMigrationTaskStatsParams): Promise { + const dataStats = await dataClient.getStats(migrationId); + const status = this.getTaskStatus(migrationId, dataStats.rules); + return { status, ...dataStats }; + } + + /** Returns the stats of all migrations */ + async getAllStats({ + dataClient, + }: RuleMigrationAllTaskStatsParams): Promise { + const allDataStats = await dataClient.getAllStats(); + return allDataStats.map((dataStats) => { + const status = this.getTaskStatus(dataStats.migration_id, dataStats.rules); + return { status, ...dataStats }; + }); + } + + private getTaskStatus( + migrationId: string, + dataStats: RuleMigrationDataStats['rules'] + ): RuleMigrationTaskStats['status'] { + if (this.migrationsRunning.has(migrationId)) { + return 'running'; + } + if (dataStats.pending === dataStats.total) { + return 'ready'; + } + if (dataStats.completed + dataStats.failed === dataStats.total) { + return 'finished'; + } + return 'stopped'; + } + + /** Stops one running migration */ + async stop({ + migrationId, + dataClient, + }: RuleMigrationTaskStopParams): Promise { + try { + const migrationRunning = this.migrationsRunning.get(migrationId); + if (migrationRunning) { + migrationRunning.abortController.abort(); + return { exists: true, stopped: true }; + } + + const { rules } = await dataClient.getStats(migrationId); + if (rules.total > 0) { + return { exists: true, stopped: false }; + } + return { exists: false, stopped: false }; + } catch (err) { + this.taskLogger.error(`Error stopping migration ID:${migrationId}`, err); + return { exists: true, stopped: false }; + } + } + + /** Stops all running migrations */ + stopAll() { + this.migrationsRunning.forEach((migrationRunning) => { + migrationRunning.abortController.abort(); + }); + this.migrationsRunning.clear(); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/types.ts new file mode 100644 index 0000000000000..e26a5b7216f48 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/types.ts @@ -0,0 +1,70 @@ +/* + * 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 { AuthenticatedUser, SavedObjectsClientContract } from '@kbn/core/server'; +import type { RunnableConfig } from '@langchain/core/runnables'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; +import type { RuleMigrationsDataClient } from '../data_stream/rule_migrations_data_client'; +import type { getRuleMigrationAgent } from './agent'; + +export type MigrationAgent = ReturnType; + +export interface RuleMigrationTaskStartParams { + migrationId: string; + currentUser: AuthenticatedUser; + connectorId: string; + invocationConfig: RunnableConfig; + inferenceClient: InferenceClient; + actionsClient: ActionsClient; + rulesClient: RulesClient; + soClient: SavedObjectsClientContract; + dataClient: RuleMigrationsDataClient; +} + +export interface RuleMigrationTaskPrepareParams { + connectorId: string; + inferenceClient: InferenceClient; + actionsClient: ActionsClient; + rulesClient: RulesClient; + soClient: SavedObjectsClientContract; + abortController: AbortController; +} + +export interface RuleMigrationTaskRunParams { + migrationId: string; + currentUser: AuthenticatedUser; + invocationConfig: RunnableConfig; + agent: MigrationAgent; + dataClient: RuleMigrationsDataClient; + abortController: AbortController; +} + +export interface RuleMigrationTaskStopParams { + migrationId: string; + dataClient: RuleMigrationsDataClient; +} + +export interface RuleMigrationTaskStatsParams { + migrationId: string; + dataClient: RuleMigrationsDataClient; +} + +export interface RuleMigrationAllTaskStatsParams { + dataClient: RuleMigrationsDataClient; +} + +export interface RuleMigrationTaskStartResult { + started: boolean; + exists: boolean; +} + +export interface RuleMigrationTaskStopResult { + stopped: boolean; + exists: boolean; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/actions_client_chat.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/actions_client_chat.ts new file mode 100644 index 0000000000000..204978c901df6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/actions_client_chat.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionsClientSimpleChatModel } from '@kbn/langchain/server'; +import { + ActionsClientBedrockChatModel, + ActionsClientChatOpenAI, + ActionsClientChatVertexAI, +} from '@kbn/langchain/server'; +import type { Logger } from '@kbn/core/server'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { ActionsClientChatOpenAIParams } from '@kbn/langchain/server/language_models/chat_openai'; +import type { CustomChatModelInput as ActionsClientBedrockChatModelParams } from '@kbn/langchain/server/language_models/bedrock_chat'; +import type { CustomChatModelInput as ActionsClientChatVertexAIParams } from '@kbn/langchain/server/language_models/gemini_chat'; +import type { CustomChatModelInput as ActionsClientSimpleChatModelParams } from '@kbn/langchain/server/language_models/simple_chat_model'; + +export type ChatModel = + | ActionsClientSimpleChatModel + | ActionsClientChatOpenAI + | ActionsClientBedrockChatModel + | ActionsClientChatVertexAI; + +export type ActionsClientChatModelClass = + | typeof ActionsClientSimpleChatModel + | typeof ActionsClientChatOpenAI + | typeof ActionsClientBedrockChatModel + | typeof ActionsClientChatVertexAI; + +export type ChatModelParams = Partial & + Partial & + Partial & + Partial & { + /** Enables the streaming mode of the response, disabled by default */ + streaming?: boolean; + }; + +const llmTypeDictionary: Record = { + [`.gen-ai`]: `openai`, + [`.bedrock`]: `bedrock`, + [`.gemini`]: `gemini`, +}; + +export class ActionsClientChat { + constructor( + private readonly connectorId: string, + private readonly actionsClient: ActionsClient, + private readonly logger: Logger + ) {} + + public async createModel(params?: ChatModelParams): Promise { + const connector = await this.actionsClient.get({ id: this.connectorId }); + if (!connector) { + throw new Error(`Connector not found: ${this.connectorId}`); + } + + const llmType = this.getLLMType(connector.actionTypeId); + const ChatModelClass = this.getLLMClass(llmType); + + const model = new ChatModelClass({ + actionsClient: this.actionsClient, + connectorId: this.connectorId, + logger: this.logger, + llmType, + model: connector.config?.defaultModel, + ...params, + streaming: params?.streaming ?? false, // disabling streaming by default, for some reason is enabled when omitted + }); + return model; + } + + private getLLMType(actionTypeId: string): string | undefined { + if (llmTypeDictionary[actionTypeId]) { + return llmTypeDictionary[actionTypeId]; + } + throw new Error(`Unknown LLM type for action type ID: ${actionTypeId}`); + } + + private getLLMClass(llmType?: string): ActionsClientChatModelClass { + switch (llmType) { + case 'bedrock': + return ActionsClientBedrockChatModel; + case 'gemini': + return ActionsClientChatVertexAI; + case 'openai': + default: + return ActionsClientChatOpenAI; + } + } +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts new file mode 100644 index 0000000000000..55256d0ad0fdd --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts @@ -0,0 +1,105 @@ +/* + * 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 { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { PrebuiltRulesMapByName } from './prebuilt_rules'; +import { filterPrebuiltRules, retrievePrebuiltRulesMap } from './prebuilt_rules'; +import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; + +jest.mock( + '../../../../detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client', + () => ({ createPrebuiltRuleObjectsClient: jest.fn() }) +); +jest.mock( + '../../../../detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client', + () => ({ createPrebuiltRuleAssetsClient: jest.fn() }) +); + +const mitreAttackIds = 'T1234'; +const rule1 = { + name: 'rule one', + id: 'rule1', + threat: [ + { + framework: 'MITRE ATT&CK', + technique: [{ id: mitreAttackIds, name: 'tactic one' }], + }, + ], +}; +const rule2 = { + name: 'rule two', + id: 'rule2', +}; + +const defaultRuleVersionsTriad = new Map([ + ['rule1', { target: rule1 }], + ['rule2', { target: rule2, current: rule2 }], +]); +const mockFetchRuleVersionsTriad = jest.fn().mockResolvedValue(defaultRuleVersionsTriad); +jest.mock( + '../../../../detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad', + () => ({ + fetchRuleVersionsTriad: () => mockFetchRuleVersionsTriad(), + }) +); + +const defaultParams = { + soClient: savedObjectsClientMock.create(), + rulesClient: rulesClientMock.create(), +}; + +describe('retrievePrebuiltRulesMap', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('when prebuilt rule is installed', () => { + it('should return isInstalled flag', async () => { + const prebuiltRulesMap = await retrievePrebuiltRulesMap(defaultParams); + expect(prebuiltRulesMap.size).toBe(2); + expect(prebuiltRulesMap.get('rule one')).toEqual( + expect.objectContaining({ installedRuleId: undefined }) + ); + expect(prebuiltRulesMap.get('rule two')).toEqual( + expect.objectContaining({ installedRuleId: rule2.id }) + ); + }); + }); +}); + +describe('filterPrebuiltRules', () => { + let prebuiltRulesMap: PrebuiltRulesMapByName; + + beforeEach(async () => { + prebuiltRulesMap = await retrievePrebuiltRulesMap(defaultParams); + jest.clearAllMocks(); + }); + + describe('when splunk rule contains empty mitreAttackIds', () => { + it('should return empty rules map', async () => { + const filteredPrebuiltRules = filterPrebuiltRules(prebuiltRulesMap, []); + expect(filteredPrebuiltRules.size).toBe(0); + }); + }); + + describe('when splunk rule does not match mitreAttackIds', () => { + it('should return empty rules map', async () => { + const filteredPrebuiltRules = filterPrebuiltRules(prebuiltRulesMap, [`${mitreAttackIds}_2`]); + expect(filteredPrebuiltRules.size).toBe(0); + }); + }); + + describe('when splunk rule contains matching mitreAttackIds', () => { + it('should return the filtered rules map', async () => { + const filteredPrebuiltRules = filterPrebuiltRules(prebuiltRulesMap, [mitreAttackIds]); + expect(filteredPrebuiltRules.size).toBe(1); + expect(filteredPrebuiltRules.get('rule one')).toEqual( + expect.objectContaining({ rule: rule1 }) + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts new file mode 100644 index 0000000000000..ade6632aaa5b5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RulesClient } from '@kbn/alerting-plugin/server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { PrebuiltRuleAsset } from '../../../../detection_engine/prebuilt_rules'; +import { fetchRuleVersionsTriad } from '../../../../detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad'; +import { createPrebuiltRuleObjectsClient } from '../../../../detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client'; +import { createPrebuiltRuleAssetsClient } from '../../../../detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; + +export interface PrebuiltRuleMapped { + rule: PrebuiltRuleAsset; + installedRuleId?: string; +} + +export type PrebuiltRulesMapByName = Map; + +interface RetrievePrebuiltRulesParams { + soClient: SavedObjectsClientContract; + rulesClient: RulesClient; +} + +export const retrievePrebuiltRulesMap = async ({ + soClient, + rulesClient, +}: RetrievePrebuiltRulesParams): Promise => { + const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); + const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + + const prebuiltRulesMap = await fetchRuleVersionsTriad({ + ruleAssetsClient, + ruleObjectsClient, + }); + const prebuiltRulesByName: PrebuiltRulesMapByName = new Map(); + prebuiltRulesMap.forEach((ruleVersions) => { + const rule = ruleVersions.target || ruleVersions.current; + if (rule) { + prebuiltRulesByName.set(rule.name, { + rule, + installedRuleId: ruleVersions.current?.id, + }); + } + }); + return prebuiltRulesByName; +}; + +export const filterPrebuiltRules = ( + prebuiltRulesByName: PrebuiltRulesMapByName, + mitreAttackIds: string[] +) => { + const filteredPrebuiltRulesByName = new Map(); + if (mitreAttackIds?.length) { + // If this rule has MITRE ATT&CK IDs, remove unrelated prebuilt rules + prebuiltRulesByName.forEach(({ rule }, ruleName) => { + const mitreAttackThreat = rule.threat?.filter( + ({ framework }) => framework === 'MITRE ATT&CK' + ); + if (!mitreAttackThreat) { + // If this rule has no MITRE ATT&CK reference we skip it + return; + } + + const sameTechnique = mitreAttackThreat.find((threat) => + threat.technique?.some(({ id }) => mitreAttackIds?.includes(id)) + ); + + if (sameTechnique) { + filteredPrebuiltRulesByName.set(ruleName, prebuiltRulesByName.get(ruleName)); + } + }); + } + return filteredPrebuiltRulesByName; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts index 1892032a21723..78ec2ef89c7a3 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts @@ -5,10 +5,29 @@ * 2.0. */ -import type { BulkResponse, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import type { IClusterClient, KibanaRequest } from '@kbn/core/server'; +import type { + AuthenticatedUser, + IClusterClient, + KibanaRequest, + SavedObjectsClientContract, +} from '@kbn/core/server'; import type { Subject } from 'rxjs'; -import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; +import type { RunnableConfig } from '@langchain/core/runnables'; +import type { ActionsClient } from '@kbn/actions-plugin/server'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; +import type { + RuleMigration, + RuleMigrationAllTaskStats, + RuleMigrationTaskStats, +} from '../../../../common/siem_migrations/model/rule_migration.gen'; +import type { RuleMigrationsDataClient } from './data_stream/rule_migrations_data_client'; +import type { RuleMigrationTaskStopResult, RuleMigrationTaskStartResult } from './task/types'; + +export interface StoredRuleMigration extends RuleMigration { + _id: string; + _index: string; +} export interface SiemRulesMigrationsSetupParams { esClusterClient: IClusterClient; @@ -16,15 +35,28 @@ export interface SiemRulesMigrationsSetupParams { tasksTimeoutMs?: number; } -export interface SiemRuleMigrationsGetClientParams { +export interface SiemRuleMigrationsCreateClientParams { request: KibanaRequest; + currentUser: AuthenticatedUser | null; spaceId: string; } -export interface RuleMigrationSearchParams { - migration_id?: string; +export interface SiemRuleMigrationsStartTaskParams { + migrationId: string; + connectorId: string; + invocationConfig: RunnableConfig; + inferenceClient: InferenceClient; + actionsClient: ActionsClient; + rulesClient: RulesClient; + soClient: SavedObjectsClientContract; } + export interface SiemRuleMigrationsClient { - create: (body: RuleMigration[]) => Promise; - search: (params: RuleMigrationSearchParams) => Promise; + data: RuleMigrationsDataClient; + task: { + start: (params: SiemRuleMigrationsStartTaskParams) => Promise; + stop: (migrationId: string) => Promise; + getStats: (migrationId: string) => Promise; + getAllStats: () => Promise; + }; } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.test.ts index 3d9e5b9fe179b..adf77756cce34 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.test.ts @@ -8,9 +8,15 @@ import { loggingSystemMock, elasticsearchServiceMock, httpServerMock, + securityServiceMock, } from '@kbn/core/server/mocks'; import { SiemMigrationsService } from './siem_migrations_service'; -import { MockSiemRuleMigrationsService, mockSetup, mockGetClient } from './rules/__mocks__/mocks'; +import { + MockSiemRuleMigrationsService, + mockSetup, + mockCreateClient, + mockStop, +} from './rules/__mocks__/mocks'; import type { ConfigType } from '../../config'; jest.mock('./rules/siem_rule_migrations_service'); @@ -25,6 +31,7 @@ describe('SiemMigrationsService', () => { let siemMigrationsService: SiemMigrationsService; const kibanaVersion = '8.16.0'; + const currentUser = securityServiceMock.createMockAuthenticatedUser(); const esClusterClient = elasticsearchServiceMock.createClusterClient(); const logger = loggingSystemMock.createLogger(); @@ -57,17 +64,22 @@ describe('SiemMigrationsService', () => { }); }); - describe('when createClient is called', () => { + describe('when createRulesClient is called', () => { it('should create rules client', async () => { - const request = httpServerMock.createKibanaRequest(); - siemMigrationsService.createClient({ spaceId: 'default', request }); - expect(mockGetClient).toHaveBeenCalledWith({ spaceId: 'default', request }); + const createRulesClientParams = { + spaceId: 'default', + request: httpServerMock.createKibanaRequest(), + currentUser, + }; + siemMigrationsService.createRulesClient(createRulesClientParams); + expect(mockCreateClient).toHaveBeenCalledWith(createRulesClientParams); }); }); describe('when stop is called', () => { it('should trigger the pluginStop subject', async () => { siemMigrationsService.stop(); + expect(mockStop).toHaveBeenCalled(); expect(mockReplaySubject$.next).toHaveBeenCalled(); expect(mockReplaySubject$.complete).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.ts index b84281eb13d9b..7a85dd625feec 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/siem_migrations_service.ts @@ -9,11 +9,8 @@ import type { Logger } from '@kbn/core/server'; import { ReplaySubject, type Subject } from 'rxjs'; import type { ConfigType } from '../../config'; import { SiemRuleMigrationsService } from './rules/siem_rule_migrations_service'; -import type { - SiemMigrationsClient, - SiemMigrationsSetupParams, - SiemMigrationsGetClientParams, -} from './types'; +import type { SiemMigrationsSetupParams, SiemMigrationsCreateClientParams } from './types'; +import type { SiemRuleMigrationsClient } from './rules/types'; export class SiemMigrationsService { private pluginStop$: Subject; @@ -30,13 +27,12 @@ export class SiemMigrationsService { } } - createClient(params: SiemMigrationsGetClientParams): SiemMigrationsClient { - return { - rules: this.rules.getClient(params), - }; + createRulesClient(params: SiemMigrationsCreateClientParams): SiemRuleMigrationsClient { + return this.rules.createClient(params); } stop() { + this.rules.stop(); this.pluginStop$.next(); this.pluginStop$.complete(); } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/types.ts index b5647ff65e214..d2af1e2518722 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/types.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/types.ts @@ -6,15 +6,11 @@ */ import type { IClusterClient } from '@kbn/core/server'; -import type { SiemRuleMigrationsClient, SiemRuleMigrationsGetClientParams } from './rules/types'; +import type { SiemRuleMigrationsCreateClientParams } from './rules/types'; export interface SiemMigrationsSetupParams { esClusterClient: IClusterClient; tasksTimeoutMs?: number; } -export type SiemMigrationsGetClientParams = SiemRuleMigrationsGetClientParams; - -export interface SiemMigrationsClient { - rules: SiemRuleMigrationsClient; -} +export type SiemMigrationsCreateClientParams = SiemRuleMigrationsCreateClientParams; diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index c7ec67c1b07fc..c178f0654d9bd 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -45,6 +45,7 @@ import type { SharePluginStart } from '@kbn/share-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; import type { PluginSetup as UnifiedSearchServerPluginSetup } from '@kbn/unified-search-plugin/server'; import type { ElasticAssistantPluginStart } from '@kbn/elastic-assistant-plugin/server'; +import type { InferenceServerStart } from '@kbn/inference-plugin/server'; import type { ProductFeaturesService } from './lib/product_features_service/product_features_service'; import type { ExperimentalFeatures } from '../common'; @@ -88,6 +89,7 @@ export interface SecuritySolutionPluginStartDependencies { telemetry?: TelemetryPluginStart; share: SharePluginStart; actions: ActionsPluginStartContract; + inference: InferenceServerStart; } export interface SecuritySolutionPluginSetup { diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index 2907c3a57ac72..eb1fed5826158 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -168,10 +168,16 @@ export class RequestContextFactory implements IRequestContextFactory { }) ), - getSiemMigrationsClient: memoize(() => - siemMigrationsService.createClient({ request, spaceId: getSpaceId() }) + getSiemRuleMigrationsClient: memoize(() => + siemMigrationsService.createRulesClient({ + request, + currentUser: coreContext.security.authc.getCurrentUser(), + spaceId: getSpaceId(), + }) ), + getInferenceClient: memoize(() => startPlugins.inference.getClient({ request })), + getExceptionListClient: () => { if (!lists) { return null; diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts index 5d8a168548a22..97b35133f8242 100644 --- a/x-pack/plugins/security_solution/server/types.ts +++ b/x-pack/plugins/security_solution/server/types.ts @@ -20,6 +20,7 @@ import type { AlertsClient, IRuleDataService } from '@kbn/rule-registry-plugin/s import type { Readable } from 'stream'; import type { AuditLogger } from '@kbn/security-plugin-types-server'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import type { Immutable } from '../common/endpoint/types'; import { AppClient } from './client'; @@ -36,7 +37,7 @@ import type { RiskScoreDataClient } from './lib/entity_analytics/risk_score/risk import type { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality'; import type { IDetectionRulesClient } from './lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface'; import type { EntityStoreDataClient } from './lib/entity_analytics/entity_store/entity_store_data_client'; -import type { SiemMigrationsClient } from './lib/siem_migrations/types'; +import type { SiemRuleMigrationsClient } from './lib/siem_migrations/rules/types'; export { AppClient }; export interface SecuritySolutionApiRequestHandlerContext { @@ -60,7 +61,8 @@ export interface SecuritySolutionApiRequestHandlerContext { getRiskScoreDataClient: () => RiskScoreDataClient; getAssetCriticalityDataClient: () => AssetCriticalityDataClient; getEntityStoreDataClient: () => EntityStoreDataClient; - getSiemMigrationsClient: () => SiemMigrationsClient; + getSiemRuleMigrationsClient: () => SiemRuleMigrationsClient; + getInferenceClient: () => InferenceClient; } export type SecuritySolutionRequestHandlerContext = CustomRequestHandlerContext<{ diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index f8fdcfcd8f438..62b77b7c02f18 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -228,5 +228,6 @@ "@kbn/data-stream-adapter", "@kbn/core-lifecycle-server", "@kbn/core-user-profile-common", + "@kbn/langchain", ] } diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 4a7efdc167299..67dcee5be34dc 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -95,6 +95,8 @@ import { GetRuleExecutionResultsRequestQueryInput, GetRuleExecutionResultsRequestParamsInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen'; +import { GetRuleMigrationRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rules_migration.gen'; +import { GetRuleMigrationStatsRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rules_migration.gen'; import { GetTimelineRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timeline/get_timeline_route.gen'; import { GetTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timelines/get_timelines_route.gen'; import { ImportRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen'; @@ -127,7 +129,12 @@ import { SetAlertAssigneesRequestBodyInput } from '@kbn/security-solution-plugin import { SetAlertsStatusRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen'; import { SetAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen'; import { StartEntityEngineRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/start.gen'; +import { + StartRuleMigrationRequestParamsInput, + StartRuleMigrationRequestBodyInput, +} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rules_migration.gen'; import { StopEntityEngineRequestParamsInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/stop.gen'; +import { StopRuleMigrationRequestParamsInput } from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rules_migration.gen'; import { SuggestUserProfilesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/users/suggest_user_profiles_route.gen'; import { TriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; import { UpdateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.gen'; @@ -782,6 +789,16 @@ finalize it. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + /** + * Retrieves the rule migrations stats for all migrations stored in the system + */ + getAllStatsRuleMigration(kibanaSpace: string = 'default') { + return supertest + .get(routeWithNamespace('/internal/siem_migrations/rules/stats', kibanaSpace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, /** * Get the asset criticality record for a specific entity. */ @@ -939,11 +956,31 @@ finalize it. .query(props.query); }, /** - * Retrieves the rule migrations stored in the system + * Retrieves the rule documents stored in the system given the rule migration id */ - getRuleMigration(kibanaSpace: string = 'default') { + getRuleMigration(props: GetRuleMigrationProps, kibanaSpace: string = 'default') { return supertest - .get(routeWithNamespace('/internal/siem_migrations/rules', kibanaSpace)) + .get( + routeWithNamespace( + replaceParams('/internal/siem_migrations/rules/{migration_id}', props.params), + kibanaSpace + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Retrieves the stats of a SIEM rules migration using the migration id provided + */ + getRuleMigrationStats(props: GetRuleMigrationStatsProps, kibanaSpace: string = 'default') { + return supertest + .get( + routeWithNamespace( + replaceParams('/internal/siem_migrations/rules/{migration_id}/stats', props.params), + kibanaSpace + ) + ) .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); @@ -1314,6 +1351,22 @@ detection engine rules. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Starts a SIEM rules migration using the migration id provided + */ + startRuleMigration(props: StartRuleMigrationProps, kibanaSpace: string = 'default') { + return supertest + .put( + routeWithNamespace( + replaceParams('/internal/siem_migrations/rules/{migration_id}/start', props.params), + kibanaSpace + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, stopEntityEngine(props: StopEntityEngineProps, kibanaSpace: string = 'default') { return supertest .post( @@ -1326,6 +1379,21 @@ detection engine rules. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Stops a running SIEM rules migration using the migration id provided + */ + stopRuleMigration(props: StopRuleMigrationProps, kibanaSpace: string = 'default') { + return supertest + .put( + routeWithNamespace( + replaceParams('/internal/siem_migrations/rules/{migration_id}/stop', props.params), + kibanaSpace + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, /** * Suggests user profiles. */ @@ -1544,6 +1612,12 @@ export interface GetRuleExecutionResultsProps { query: GetRuleExecutionResultsRequestQueryInput; params: GetRuleExecutionResultsRequestParamsInput; } +export interface GetRuleMigrationProps { + params: GetRuleMigrationRequestParamsInput; +} +export interface GetRuleMigrationStatsProps { + params: GetRuleMigrationStatsRequestParamsInput; +} export interface GetTimelineProps { query: GetTimelineRequestQueryInput; } @@ -1616,9 +1690,16 @@ export interface SetAlertTagsProps { export interface StartEntityEngineProps { params: StartEntityEngineRequestParamsInput; } +export interface StartRuleMigrationProps { + params: StartRuleMigrationRequestParamsInput; + body: StartRuleMigrationRequestBodyInput; +} export interface StopEntityEngineProps { params: StopEntityEngineRequestParamsInput; } +export interface StopRuleMigrationProps { + params: StopRuleMigrationRequestParamsInput; +} export interface SuggestUserProfilesProps { query: SuggestUserProfilesRequestQueryInput; } From 4c649d9f14d6631d23931fc0d32dc28017cbad38 Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Wed, 6 Nov 2024 09:38:01 -0800 Subject: [PATCH 30/53] [Detection Engine][FTR] Add FTR exception list tests for Serverless prebuilt roles (#198420) ## Summary Adds FTR tests that check our Serverless prebuilt roles against our exception list endpoints. We have had little coverage or visibility to know if any changes made in elasticsearch-controller introduce a bug in our prebuilt roles. We could certainly discuss how such tests should be organized - I chose to create an `authentication` folder that then has a matching folder for the other sections and a file for each prebuilt role. With us nearing GA, I'd like to prioritize having coverage and following up with any improvements. --- .../ftr_security_serverless_configs.yml | 4 +- .../mki_periodic_detection_engine.yml | 45 ++++++ .../mki_quality_gate_detection_engine.yml | 82 +++++++--- .../package.json | 18 +++ .../common/essentials_tier/admin.ts | 111 +++++++++++++ .../configs/serverless.config.ts | 16 ++ .../common/essentials_tier/editor.ts | 113 +++++++++++++ .../endpoint_operations_analyst.ts | 113 +++++++++++++ .../endpoint_policy_manager.ts | 113 +++++++++++++ .../common/essentials_tier/index.ts | 24 +++ .../essentials_tier/platform_engineer.ts | 113 +++++++++++++ .../common/essentials_tier/rule_author.ts | 113 +++++++++++++ .../common/essentials_tier/soc_manager.ts | 113 +++++++++++++ .../essentials_tier/threat_intel_analyst.ts | 113 +++++++++++++ .../common/essentials_tier/tier_1_analyst.ts | 113 +++++++++++++ .../common/essentials_tier/tier_2_analyst.ts | 113 +++++++++++++ .../common/essentials_tier/tier_3_analyst.ts | 113 +++++++++++++ .../common/essentials_tier/viewer.ts | 113 +++++++++++++ .../exceptions/items/essentials_tier/admin.ts | 150 +++++++++++++++++ .../configs/serverless.config.ts | 16 ++ .../items/essentials_tier/editor.ts | 152 ++++++++++++++++++ .../endpoint_operations_analyst.ts | 152 ++++++++++++++++++ .../endpoint_policy_manager.ts | 152 ++++++++++++++++++ .../exceptions/items/essentials_tier/index.ts | 24 +++ .../essentials_tier/platform_engineer.ts | 152 ++++++++++++++++++ .../items/essentials_tier/rule_author.ts | 152 ++++++++++++++++++ .../items/essentials_tier/soc_manager.ts | 152 ++++++++++++++++++ .../essentials_tier/threat_intel_analyst.ts | 152 ++++++++++++++++++ .../items/essentials_tier/tier_1_analyst.ts | 152 ++++++++++++++++++ .../items/essentials_tier/tier_2_analyst.ts | 152 ++++++++++++++++++ .../items/essentials_tier/tier_3_analyst.ts | 152 ++++++++++++++++++ .../items/essentials_tier/viewer.ts | 152 ++++++++++++++++++ .../exceptions/lists/essentials_tier/admin.ts | 116 +++++++++++++ .../configs/serverless.config.ts | 16 ++ .../lists/essentials_tier/editor.ts | 118 ++++++++++++++ .../endpoint_operations_analyst.ts | 122 ++++++++++++++ .../endpoint_policy_manager.ts | 122 ++++++++++++++ .../exceptions/lists/essentials_tier/index.ts | 24 +++ .../essentials_tier/platform_engineer.ts | 122 ++++++++++++++ .../lists/essentials_tier/rule_author.ts | 122 ++++++++++++++ .../lists/essentials_tier/soc_manager.ts | 122 ++++++++++++++ .../essentials_tier/threat_intel_analyst.ts | 122 ++++++++++++++ .../lists/essentials_tier/tier_1_analyst.ts | 122 ++++++++++++++ .../lists/essentials_tier/tier_2_analyst.ts | 122 ++++++++++++++ .../lists/essentials_tier/tier_3_analyst.ts | 122 ++++++++++++++ .../lists/essentials_tier/viewer.ts | 118 ++++++++++++++ .../lists/duplicate_exception_list.ts | 7 +- 47 files changed, 4879 insertions(+), 23 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/admin.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/editor.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_operations_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_policy_manager.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/platform_engineer.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/rule_author.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/soc_manager.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/threat_intel_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_1_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_2_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_3_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/viewer.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/admin.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/editor.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_operations_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_policy_manager.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/platform_engineer.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/rule_author.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/soc_manager.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/threat_intel_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_1_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_2_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_3_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/viewer.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/admin.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/editor.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_operations_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_policy_manager.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/platform_engineer.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/rule_author.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/soc_manager.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/threat_intel_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_1_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_2_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_3_analyst.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/viewer.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 6b1c222382687..89550c59e6bb8 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -11,7 +11,6 @@ disabled: - x-pack/test/osquery_cypress/serverless_cli_config.ts - x-pack/test/security_solution_cypress/serverless_config.ts - # Playwright - x-pack/test/security_solution_playwright/serverless_config.ts @@ -93,6 +92,9 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/serverless.config.ts diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml index 56b1904925f04..2f6e329524c5d 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_detection_engine.yml @@ -50,6 +50,51 @@ steps: - exit_status: '1' limit: 2 + - label: Running exception_lists:auth:lists:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:auth:lists:qa:serverless + key: exception_lists:auth:lists:qa:serverless + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-4 + preemptible: true + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running exception_lists:auth:common:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:auth:common:qa:serverless + key: exception_lists:auth:common:qa:serverless + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-4 + preemptible: true + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running exception_lists:auth:items:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:auth:items:qa:serverless + key: exception_lists:auth:items:qa:serverless + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-4 + preemptible: true + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + - label: Running lists_items:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh lists_items:qa:serverless key: lists_items:qa:serverless diff --git a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml index 023099dd99392..de98c0ffffd3f 100644 --- a/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml +++ b/.buildkite/pipelines/security_solution_quality_gate/mki_quality_gate/mki_quality_gate_detection_engine.yml @@ -1,12 +1,12 @@ steps: - - group: "Cypress MKI - Detection Engine" + - group: 'Cypress MKI - Detection Engine' key: cypress_test_detections_engine steps: - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine - label: "Cypress MKI - Detection Engine" + label: 'Cypress MKI - Detection Engine' key: test_detection_engine env: - BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" + BK_TEST_SUITE_KEY: 'serverless-cypress-detection-engine' agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod @@ -17,10 +17,10 @@ steps: parallelism: 1 - command: .buildkite/scripts/pipelines/security_solution_quality_gate/security_solution_cypress/mki_security_solution_cypress.sh cypress:run:qa:serverless:detection_engine:exceptions - label: "Cypress MKI - Detection Engine - Exceptions" + label: 'Cypress MKI - Detection Engine - Exceptions' key: test_detection_engine_exceptions env: - BK_TEST_SUITE_KEY: "serverless-cypress-detection-engine" + BK_TEST_SUITE_KEY: 'serverless-cypress-detection-engine' agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod @@ -30,7 +30,7 @@ steps: timeout_in_minutes: 300 parallelism: 1 - - group: "API MKI - Detection Engine" + - group: 'API MKI - Detection Engine' key: api_test_detections_engine steps: - label: Running exception_lists_items:qa:serverless:release @@ -44,7 +44,49 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' + limit: 2 + + - label: Running exception_lists:auth:lists:qa:serverless:release + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:auth:lists:qa:serverless:release + key: exception_lists:auth:lists:qa:serverless:release + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-4 + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running exception_lists:common:lists:qa:serverless:release + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:common:lists:qa:serverless:release + key: exception_lists:common:lists:qa:serverless:release + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-4 + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running exception_lists:items:lists:qa:serverless:release + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh exception_lists:items:lists:qa:serverless:release + key: exception_lists:items:lists:qa:serverless:release + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: n2-standard-4 + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' limit: 2 - label: Running lists_items:qa:serverless:release @@ -58,7 +100,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running user_roles:qa:serverless:release @@ -72,7 +114,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running telemetry:qa:serverless:release @@ -86,7 +128,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_workflows:essentials:qa:serverless:release @@ -100,7 +142,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_operators_date_types:essentials:qa:serverless:release @@ -156,7 +198,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_operators_keyword:essentials:qa:serverless:release @@ -170,7 +212,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_operators_ips:essentials:qa:serverless:release @@ -184,7 +226,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_operators_long:essentials:qa:serverless:release @@ -198,7 +240,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running exception_operators_text:essentials:qa:serverless:release @@ -212,7 +254,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running actions:qa:serverless:release @@ -226,7 +268,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running alerts:qa:serverless:release @@ -240,7 +282,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running alerts:essentials:qa:serverless:release @@ -254,7 +296,7 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 - label: Running rule_execution_logic:eql:qa:serverless:release @@ -366,5 +408,5 @@ steps: timeout_in_minutes: 120 retry: automatic: - - exit_status: "1" + - exit_status: '1' limit: 2 diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 57342fdc264c6..26f8a8ff80695 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -36,6 +36,9 @@ "initialize-server:lists:complete": "node ./scripts/index.js server lists_and_exception_lists trial_license_complete_tier", "run-tests:lists:complete": "node ./scripts/index.js runner lists_and_exception_lists trial_license_complete_tier", + "initialize-server:lists:essentials_only": "node ./scripts/index.js server lists_and_exception_lists essentials_tier", + "run-tests:lists:essentials_only": "node ./scripts/index.js runner lists_and_exception_lists essentials_tier", + "initialize-server:edr-workflows": "node ./scripts/index.js server edr_workflows trial_license_complete_tier", "run-tests:edr-workflows": "node ./scripts/index.js runner edr_workflows trial_license_complete_tier", @@ -136,6 +139,21 @@ "edr_workflows:response_actions:server:ess": "npm run initialize-server:edr-workflows response_actions ess", "edr_workflows:response_actions:runner:ess": "npm run run-tests:edr-workflows response_actions ess essEnv", + "exception_lists:auth:lists:server:serverless": "npm run initialize-server:lists:essentials_only authorization/exceptions/lists serverless", + "exception_lists:auth:lists:runner:serverless": "npm run run-tests:lists:essentials_only authorization/exceptions/lists serverless serverlessEnv", + "exception_lists:auth:lists:qa:serverless": "npm run run-tests:lists:essentials_only authorization/exceptions/lists serverless qaPeriodicEnv", + "exception_lists:auth:lists:qa:serverless:release": "npm run run-tests:lists:essentials_only authorization/exceptions/lists serverless qaEnv", + + "exception_lists:auth:common:server:serverless": "npm run initialize-server:lists:essentials_only authorization/exceptions/common serverless", + "exception_lists:auth:common:runner:serverless": "npm run run-tests:lists:essentials_only authorization/exceptions/common serverless serverlessEnv", + "exception_lists:auth:common:qa:serverless": "npm run run-tests:lists:essentials_only authorization/exceptions/common serverless qaPeriodicEnv", + "exception_lists:auth:common:qa:serverless:release": "npm run run-tests:lists:essentials_only authorization/exceptions/common serverless qaEnv", + + "exception_lists:auth:items:server:serverless": "npm run initialize-server:lists:essentials_only authorization/exceptions/items serverless", + "exception_lists:auth:items:runner:serverless": "npm run run-tests:lists:essentials_only authorization/exceptions/items serverless serverlessEnv", + "exception_lists:auth:items:qa:serverless": "npm run run-tests:lists:essentials_only authorization/exceptions/items serverless qaPeriodicEnv", + "exception_lists:auth:items:qa:serverless:release": "npm run run-tests:lists:essentials_only authorization/exceptions/items serverless qaEnv", + "exception_lists_items:server:serverless": "npm run initialize-server:lists:complete exception_lists_items serverless", "exception_lists_items:runner:serverless": "npm run run-tests:lists:complete exception_lists_items serverless serverlessEnv", "exception_lists_items:qa:serverless": "npm run run-tests:lists:complete exception_lists_items serverless qaPeriodicEnv", diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/admin.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/admin.ts new file mode 100644 index 0000000000000..a9deaeccc00ba --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/admin.ts @@ -0,0 +1,111 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + + describe('@serverless @serverlessQA admin exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for admin', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await admin + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for admin', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await admin + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for admin', async () => { + await admin + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..2fcd8d232299f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine - Exception List and Items Authentication Tests - Serverless Env - Essentials Tier', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/editor.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/editor.ts new file mode 100644 index 0000000000000..b136ba3cd5e86 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/editor.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let editor: TestAgent; + + describe('@serverless @serverlessQA editor exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + editor = await utils.createSuperTest('editor'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for editor', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await editor + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for editor', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await editor + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for editor', async () => { + await editor + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_operations_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_operations_analyst.ts new file mode 100644 index 0000000000000..98b0ac438c513 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_operations_analyst.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let endpointOperationsAnalyst: TestAgent; + + describe('@serverless @serverlessQA endpoint_operations_analyst exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + endpointOperationsAnalyst = await utils.createSuperTest('endpoint_operations_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await endpointOperationsAnalyst + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await endpointOperationsAnalyst + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + await endpointOperationsAnalyst + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_policy_manager.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_policy_manager.ts new file mode 100644 index 0000000000000..697c08055bc95 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/endpoint_policy_manager.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let endpointPolicyManager: TestAgent; + + describe('@serverless @serverlessQA endpoint_policy_manager exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + endpointPolicyManager = await utils.createSuperTest('endpoint_policy_manager'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await endpointPolicyManager + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await endpointPolicyManager + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + await endpointPolicyManager + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/index.ts new file mode 100644 index 0000000000000..272ba9e4ffa5f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Exception list and items APIs Authentication - Complete Tier', function () { + loadTestFile(require.resolve('./tier_1_analyst')); + loadTestFile(require.resolve('./tier_2_analyst')); + loadTestFile(require.resolve('./threat_intel_analyst')); + loadTestFile(require.resolve('./tier_3_analyst')); + loadTestFile(require.resolve('./viewer')); + loadTestFile(require.resolve('./rule_author')); + loadTestFile(require.resolve('./soc_manager')); + loadTestFile(require.resolve('./endpoint_operations_analyst')); + loadTestFile(require.resolve('./endpoint_policy_manager')); + loadTestFile(require.resolve('./platform_engineer')); + loadTestFile(require.resolve('./editor')); + loadTestFile(require.resolve('./admin')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/platform_engineer.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/platform_engineer.ts new file mode 100644 index 0000000000000..7168027c45025 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/platform_engineer.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let platformEngineer: TestAgent; + + describe('@serverless @serverlessQA platform_engineer exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + platformEngineer = await utils.createSuperTest('platform_engineer'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for platform_engineer', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await platformEngineer + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for platform_engineer', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await platformEngineer + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for platform_engineer', async () => { + await platformEngineer + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/rule_author.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/rule_author.ts new file mode 100644 index 0000000000000..d7f164d2d0fcf --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/rule_author.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let ruleAuthor: TestAgent; + + describe('@serverless @serverlessQA rule_author exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + ruleAuthor = await utils.createSuperTest('rule_author'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for rule_author', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await ruleAuthor + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for rule_author', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await ruleAuthor + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for rule_author', async () => { + await ruleAuthor + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/soc_manager.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/soc_manager.ts new file mode 100644 index 0000000000000..1c94880578d2c --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/soc_manager.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let socManager: TestAgent; + + describe('@serverless @serverlessQA soc_manager exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + socManager = await utils.createSuperTest('soc_manager'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for soc_manager', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await socManager + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for soc_manager', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await socManager + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for soc_manager', async () => { + await socManager + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/threat_intel_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/threat_intel_analyst.ts new file mode 100644 index 0000000000000..6c4d6b4118950 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/threat_intel_analyst.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let threatIntelAnalyst: TestAgent; + + describe('@serverless @serverlessQA threat_intelligence_analyst exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + threatIntelAnalyst = await utils.createSuperTest('threat_intelligence_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await threatIntelAnalyst + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await threatIntelAnalyst + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + await threatIntelAnalyst + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_1_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_1_analyst.ts new file mode 100644 index 0000000000000..f319b7a05ce5d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_1_analyst.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t1Analyst: TestAgent; + + describe('@serverless @serverlessQA t1_analyst exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t1Analyst = await utils.createSuperTest('t1_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 403 for t1_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await t1Analyst + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('export exception list', () => { + it('should return 200 for t1_analyst', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await t1Analyst + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 403 for t1_analyst', async () => { + await t1Analyst + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_2_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_2_analyst.ts new file mode 100644 index 0000000000000..5a2fdda17c018 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_2_analyst.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t2Analyst: TestAgent; + + describe('@serverless @serverlessQA t2_analyst exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t2Analyst = await utils.createSuperTest('t2_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 403 for t2_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await t2Analyst + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('export exception list', () => { + it('should return 200 for t2_analyst', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await t2Analyst + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 403 for t2_analyst', async () => { + await t2Analyst + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_3_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_3_analyst.ts new file mode 100644 index 0000000000000..16c043239effb --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/tier_3_analyst.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t3Analyst: TestAgent; + + describe('@serverless @serverlessQA t3_analyst exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t3Analyst = await utils.createSuperTest('t3_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 200 for t3_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await t3Analyst + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('export exception list', () => { + it('should return 200 for t3_analyst', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await t3Analyst + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 200 for t3_analyst', async () => { + await t3Analyst + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/viewer.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/viewer.ts new file mode 100644 index 0000000000000..ec37e7ae59fb4 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/common/essentials_tier/viewer.ts @@ -0,0 +1,113 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getImportExceptionsListItemSchemaMock, + getImportExceptionsListSchemaMock, + toNdJsonString, +} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let viewer: TestAgent; + + describe('@serverless @serverlessQA viewer exception list and item API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + viewer = await utils.createSuperTest('viewer'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('duplicate exception list', () => { + it('should return 403 for viewer', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListDetectionSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send({ + ...getCreateExceptionListItemMinimalSchemaMock(), + list_id: getCreateExceptionListDetectionSchemaMock().list_id, + }) + .expect(200); + + await viewer + .post( + `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ + getCreateExceptionListDetectionSchemaMock().list_id + }&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('export exception list', () => { + it('should return 200 for viewer', async () => { + const { body } = await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await viewer + .post( + `${EXCEPTION_LIST_URL}/_export?id=${body.id}&list_id=${body.list_id}&namespace_type=single&include_expired_exceptions=true` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('import exception list', () => { + it('should return 403 for viewer', async () => { + await viewer + .post(`${EXCEPTION_LIST_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .attach( + 'file', + Buffer.from( + toNdJsonString([ + getImportExceptionsListSchemaMock('test_list_id'), + getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), + ]) + ), + 'exceptions.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/admin.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/admin.ts new file mode 100644 index 0000000000000..52ab99388341b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/admin.ts @@ -0,0 +1,150 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + + describe('@serverless @serverlessQA admin exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for admin', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for admin', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await admin + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for admin', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for admin', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await admin + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for admin', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await admin + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..113162887d138 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine - Exception Items Authentication Tests - Serverless Env - Essentials Tier', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/editor.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/editor.ts new file mode 100644 index 0000000000000..57e611a6a30bb --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/editor.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let editor: TestAgent; + + describe('@serverless @serverlessQA editor exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + editor = await utils.createSuperTest('editor'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for editor', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await editor + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for editor', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await editor + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for editor', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await editor + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for editor', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await editor + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for editor', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await editor + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_operations_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_operations_analyst.ts new file mode 100644 index 0000000000000..330e830a44462 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_operations_analyst.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let endpointOperationsAnalyst: TestAgent; + + describe('@serverless @serverlessQA endpoint_operations_analyst exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + endpointOperationsAnalyst = await utils.createSuperTest('endpoint_operations_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointOperationsAnalyst + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await endpointOperationsAnalyst + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointOperationsAnalyst + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await endpointOperationsAnalyst + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await endpointOperationsAnalyst + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_policy_manager.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_policy_manager.ts new file mode 100644 index 0000000000000..dfd12c24f6aeb --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/endpoint_policy_manager.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let endpointPolicyManager: TestAgent; + + describe('@serverless @serverlessQA endpoint_policy_manager exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + endpointPolicyManager = await utils.createSuperTest('endpoint_policy_manager'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for endpoint_policy_manager', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointPolicyManager + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await endpointPolicyManager + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for endpoint_policy_manager', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointPolicyManager + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await endpointPolicyManager + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await endpointPolicyManager + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/index.ts new file mode 100644 index 0000000000000..35f627cd8dede --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Exception items APIs Authentication - Complete Tier', function () { + loadTestFile(require.resolve('./tier_1_analyst')); + loadTestFile(require.resolve('./tier_2_analyst')); + loadTestFile(require.resolve('./threat_intel_analyst')); + loadTestFile(require.resolve('./tier_3_analyst')); + loadTestFile(require.resolve('./viewer')); + loadTestFile(require.resolve('./rule_author')); + loadTestFile(require.resolve('./soc_manager')); + loadTestFile(require.resolve('./endpoint_operations_analyst')); + loadTestFile(require.resolve('./endpoint_policy_manager')); + loadTestFile(require.resolve('./platform_engineer')); + loadTestFile(require.resolve('./editor')); + loadTestFile(require.resolve('./admin')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/platform_engineer.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/platform_engineer.ts new file mode 100644 index 0000000000000..2bd5b39a056d0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/platform_engineer.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let platformEngineer: TestAgent; + + describe('@serverless @serverlessQA platform_engineer exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + platformEngineer = await utils.createSuperTest('platform_engineer'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for platform_engineer', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await platformEngineer + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for platform_engineer', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await platformEngineer + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for platform_engineer', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await platformEngineer + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for platform_engineer', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await platformEngineer + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for platform_engineer', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await platformEngineer + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/rule_author.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/rule_author.ts new file mode 100644 index 0000000000000..529d4854adf14 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/rule_author.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let ruleAuthor: TestAgent; + + describe('@serverless @serverlessQA rule_author exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + ruleAuthor = await utils.createSuperTest('rule_author'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for rule_author', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await ruleAuthor + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for rule_author', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await ruleAuthor + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for rule_author', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await ruleAuthor + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for rule_author', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await ruleAuthor + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for rule_author', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await ruleAuthor + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/soc_manager.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/soc_manager.ts new file mode 100644 index 0000000000000..97826150c64a6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/soc_manager.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let socManager: TestAgent; + + describe('@serverless @serverlessQA soc_manager exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + socManager = await utils.createSuperTest('soc_manager'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for soc_manager', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await socManager + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for soc_manager', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await socManager + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for soc_manager', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await socManager + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for soc_manager', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await socManager + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for soc_manager', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await socManager + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/threat_intel_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/threat_intel_analyst.ts new file mode 100644 index 0000000000000..ebd642dfdbd15 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/threat_intel_analyst.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let threatIntelAnalyst: TestAgent; + + describe('@serverless @serverlessQA threat_intelligence_analyst exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + threatIntelAnalyst = await utils.createSuperTest('threat_intelligence_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await threatIntelAnalyst + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await threatIntelAnalyst + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await threatIntelAnalyst + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await threatIntelAnalyst + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await threatIntelAnalyst + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_1_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_1_analyst.ts new file mode 100644 index 0000000000000..9030ed349c2e8 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_1_analyst.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t1Analyst: TestAgent; + + describe('@serverless @serverlessQA t1_analyst exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t1Analyst = await utils.createSuperTest('t1_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 403 for t1_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t1Analyst + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(403); + }); + }); + + describe('delete exception item', () => { + it('should return 403 for t1_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await t1Analyst + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('find exception item', () => { + it('should return 200 for t1_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t1Analyst + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for t1_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await t1Analyst + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 403 for t1_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await t1Analyst + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_2_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_2_analyst.ts new file mode 100644 index 0000000000000..75fa6347953ef --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_2_analyst.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t2Analyst: TestAgent; + + describe('@serverless @serverlessQA t2_analyst exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t2Analyst = await utils.createSuperTest('t2_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 403 for t2_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t2Analyst + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(403); + }); + }); + + describe('delete exception item', () => { + it('should return 403 for t2_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await t2Analyst + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('find exception item', () => { + it('should return 200 for t2_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t2Analyst + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for t2_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await t2Analyst + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 403 for t2_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await t2Analyst + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_3_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_3_analyst.ts new file mode 100644 index 0000000000000..8a55f46965df5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/tier_3_analyst.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t3Analyst: TestAgent; + + describe('@serverless @serverlessQA t3_analyst exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t3Analyst = await utils.createSuperTest('t3_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 200 for t3_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t3Analyst + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception item', () => { + it('should return 200 for t3_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await t3Analyst + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception item', () => { + it('should return 200 for t3_analyst', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t3Analyst + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for t3_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await t3Analyst + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 200 for t3_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await t3Analyst + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/viewer.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/viewer.ts new file mode 100644 index 0000000000000..c03446a04a95d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/items/essentials_tier/viewer.ts @@ -0,0 +1,152 @@ +/* + * 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 TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { UpdateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { getUpdateMinimalExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_item_schema.mock'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let viewer: TestAgent; + + describe('@serverless @serverlessQA viewer exception items API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + viewer = await utils.createSuperTest('viewer'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception item', () => { + it('should return 403 for viewer', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await viewer + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(403); + }); + }); + + describe('delete exception item', () => { + it('should return 403 for viewer', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create an exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // delete the exception list item by its item_id + await viewer + .delete( + `${EXCEPTION_LIST_ITEM_URL}?item_id=${ + getCreateExceptionListItemMinimalSchemaMock().item_id + }` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('find exception item', () => { + it('should return 200 for viewer', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await viewer + .get( + `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${ + getCreateExceptionListMinimalSchemaMock().list_id + }` + ) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + }); + }); + + describe('read exception item', () => { + it('should return 200 for viewer', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item to read + const { body: createListBody } = await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + await viewer + .get(`${EXCEPTION_LIST_ITEM_URL}?id=${createListBody.id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception item', () => { + it('should return 403 for viewer', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // create a simple exception list item + await admin + .post(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListItemMinimalSchemaMock()) + .expect(200); + + // update a exception list item's name + const updatedList: UpdateExceptionListItemSchema = { + ...getUpdateMinimalExceptionListItemSchemaMock(), + name: 'some other name', + }; + + await viewer + .put(EXCEPTION_LIST_ITEM_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/admin.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/admin.ts new file mode 100644 index 0000000000000..fdee68f7ce566 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/admin.ts @@ -0,0 +1,116 @@ +/* + * 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 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + + describe('@serverless @serverlessQA admin exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for admin', async () => { + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for admin', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for admin', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await admin + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for admin', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await admin + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for admin', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await admin.put(EXCEPTION_LIST_URL).set('kbn-xsrf', 'true').send(updatedList).expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts new file mode 100644 index 0000000000000..9e8e6663f6305 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/configs/serverless.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../../config/serverless/config.base.essentials'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Detection Engine - Exception List Authentication Tests - Serverless Env - Essentials Tier', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/editor.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/editor.ts new file mode 100644 index 0000000000000..7cda40dbb5e13 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/editor.ts @@ -0,0 +1,118 @@ +/* + * 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 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let editor: TestAgent; + + describe('@serverless @serverlessQA editor exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + editor = await utils.createSuperTest('editor'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for editor', async () => { + await editor + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for editor', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await editor + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for editor', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await editor + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for editor', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await editor + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for editor', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await editor.put(EXCEPTION_LIST_URL).set('kbn-xsrf', 'true').send(updatedList).expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_operations_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_operations_analyst.ts new file mode 100644 index 0000000000000..19b4da0b5870b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_operations_analyst.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let endpointOperationsAnalyst: TestAgent; + + describe('@serverless @serverlessQA endpoint_operations_analyst exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + endpointOperationsAnalyst = await utils.createSuperTest('endpoint_operations_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + await endpointOperationsAnalyst + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointOperationsAnalyst + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await endpointOperationsAnalyst + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointOperationsAnalyst + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for endpoint_operations_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await endpointOperationsAnalyst + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_policy_manager.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_policy_manager.ts new file mode 100644 index 0000000000000..8b0735d21ab38 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/endpoint_policy_manager.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let endpointPolicyManager: TestAgent; + + describe('@serverless @serverlessQA endpoint_policy_manager exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + endpointPolicyManager = await utils.createSuperTest('endpoint_policy_manager'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + await endpointPolicyManager + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointPolicyManager + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await endpointPolicyManager + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await endpointPolicyManager + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for endpoint_policy_manager', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await endpointPolicyManager + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/index.ts new file mode 100644 index 0000000000000..d3295ee8457c1 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Exception list APIs Authentication - Complete Tier', function () { + loadTestFile(require.resolve('./tier_1_analyst')); + loadTestFile(require.resolve('./tier_2_analyst')); + loadTestFile(require.resolve('./threat_intel_analyst')); + loadTestFile(require.resolve('./tier_3_analyst')); + loadTestFile(require.resolve('./viewer')); + loadTestFile(require.resolve('./rule_author')); + loadTestFile(require.resolve('./soc_manager')); + loadTestFile(require.resolve('./endpoint_operations_analyst')); + loadTestFile(require.resolve('./endpoint_policy_manager')); + loadTestFile(require.resolve('./platform_engineer')); + loadTestFile(require.resolve('./editor')); + loadTestFile(require.resolve('./admin')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/platform_engineer.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/platform_engineer.ts new file mode 100644 index 0000000000000..a0d11f61368b7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/platform_engineer.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let platformEngineer: TestAgent; + + describe('@serverless @serverlessQA platform_engineer exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + platformEngineer = await utils.createSuperTest('platform_engineer'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for platform_engineer', async () => { + await platformEngineer + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for platform_engineer', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await platformEngineer + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for platform_engineer', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await platformEngineer + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for platform_engineer', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await platformEngineer + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for platform_engineer', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await platformEngineer + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/rule_author.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/rule_author.ts new file mode 100644 index 0000000000000..2da3ea687ef6c --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/rule_author.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let ruleAuthor: TestAgent; + + describe('@serverless @serverlessQA rule_author exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + ruleAuthor = await utils.createSuperTest('rule_author'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for rule_author', async () => { + await ruleAuthor + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for rule_author', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await ruleAuthor + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for rule_author', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await ruleAuthor + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for rule_author', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await ruleAuthor + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for rule_author', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await ruleAuthor + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/soc_manager.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/soc_manager.ts new file mode 100644 index 0000000000000..6b9e6b9234157 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/soc_manager.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let socManager: TestAgent; + + describe('@serverless @serverlessQA soc_manager exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + socManager = await utils.createSuperTest('soc_manager'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for soc_manager', async () => { + await socManager + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for soc_manager', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await socManager + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for soc_manager', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await socManager + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for soc_manager', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await socManager + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for soc_manager', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await socManager + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/threat_intel_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/threat_intel_analyst.ts new file mode 100644 index 0000000000000..a25d98252782f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/threat_intel_analyst.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let threatIntelAnalyst: TestAgent; + + describe('@serverless @serverlessQA threat_intelligence_analyst exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + threatIntelAnalyst = await utils.createSuperTest('threat_intelligence_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + await threatIntelAnalyst + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await threatIntelAnalyst + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await threatIntelAnalyst + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await threatIntelAnalyst + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for threat_intelligence_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await threatIntelAnalyst + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_1_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_1_analyst.ts new file mode 100644 index 0000000000000..60f9be5afcb87 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_1_analyst.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t1Analyst: TestAgent; + + describe('@serverless @serverlessQA t1_analyst exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t1Analyst = await utils.createSuperTest('t1_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 403 for t1_analyst', async () => { + await t1Analyst + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(403); + }); + }); + + describe('delete exception list', () => { + it('should return 403 for t1_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t1Analyst + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('find exception list', () => { + it('should return 200 for t1_analyst', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await t1Analyst + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for t1_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t1Analyst + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 403 for t1_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await t1Analyst + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_2_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_2_analyst.ts new file mode 100644 index 0000000000000..5e501e1a83512 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_2_analyst.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t2Analyst: TestAgent; + + describe('@serverless @serverlessQA t2_analyst exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t2Analyst = await utils.createSuperTest('t2_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 403 for t2_analyst', async () => { + await t2Analyst + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(403); + }); + }); + + describe('delete exception list', () => { + it('should return 403 for t2_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t2Analyst + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('find exception list', () => { + it('should return 200 for t2_analyst', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await t2Analyst + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for t2_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t2Analyst + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 403 for t2_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await t2Analyst + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_3_analyst.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_3_analyst.ts new file mode 100644 index 0000000000000..3c12223d089b7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/tier_3_analyst.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let t3Analyst: TestAgent; + + describe('@serverless @serverlessQA t3_analyst exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + t3Analyst = await utils.createSuperTest('t3_analyst'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 200 for t3_analyst', async () => { + await t3Analyst + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + }); + }); + + describe('delete exception list', () => { + it('should return 200 for t3_analyst', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t3Analyst + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('find exception list', () => { + it('should return 200 for t3_analyst', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await t3Analyst + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for t3_analyst', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await t3Analyst + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 200 for t3_analyst', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await t3Analyst + .put(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(updatedList) + .expect(200); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/viewer.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/viewer.ts new file mode 100644 index 0000000000000..e4bc47ea1b840 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/authorization/exceptions/lists/essentials_tier/viewer.ts @@ -0,0 +1,118 @@ +/* + * 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 'expect'; +import TestAgent from 'supertest/lib/agent'; +import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; +import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { getUpdateMinimalExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_exception_list_schema.mock'; +import { UpdateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + let admin: TestAgent; + let viewer: TestAgent; + + describe('@serverless @serverlessQA viewer exception list API behaviors', () => { + before(async () => { + admin = await utils.createSuperTest('admin'); + viewer = await utils.createSuperTest('viewer'); + await deleteAllExceptions(admin, log); + }); + + afterEach(async () => { + await deleteAllExceptions(admin, log); + }); + + describe('create exception list', () => { + it('should return 403 for viewer', async () => { + await viewer + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(403); + }); + }); + + describe('delete exception list', () => { + it('should return 403 for viewer', async () => { + // create an exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await viewer + .delete( + `${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}` + ) + .set('kbn-xsrf', 'true') + .expect(403); + }); + }); + + describe('find exception list', () => { + it('should return 200 for viewer', async () => { + // add a single exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // query the single exception list from _find + const { body } = await viewer + .get(`${EXCEPTION_LIST_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body.total).toEqual(1); + }); + }); + + describe('read exception list', () => { + it('should return 200 for viewer', async () => { + // create a simple exception list to read + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + await viewer + .get(`${EXCEPTION_LIST_URL}?list_id=${getCreateExceptionListMinimalSchemaMock().list_id}`) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); + + describe('update exception list', () => { + it('should return 403 for viewer', async () => { + // create a simple exception list + await admin + .post(EXCEPTION_LIST_URL) + .set('kbn-xsrf', 'true') + .send(getCreateExceptionListMinimalSchemaMock()) + .expect(200); + + // update a exception list's name + const updatedList: UpdateExceptionListSchema = { + ...getUpdateMinimalExceptionListSchemaMock(), + name: 'some other name', + }; + + await viewer.put(EXCEPTION_LIST_URL).set('kbn-xsrf', 'true').send(updatedList).expect(403); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts index 6501ac284b7ff..a9c861dfcc74a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts @@ -13,7 +13,10 @@ import { EXCEPTION_LIST_URL, } from '@kbn/securitysolution-list-constants'; import { getExceptionResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; -import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; +import { + getCreateExceptionListDetectionSchemaMock, + getCreateExceptionListMinimalSchemaMock, +} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; import { deleteAllExceptions, removeExceptionListServerGeneratedProperties } from '../../../utils'; @@ -76,7 +79,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: listBody } = await supertest .post( `${EXCEPTION_LIST_URL}/_duplicate?list_id=${ - getCreateExceptionListDetectionSchemaMock().list_id + getCreateExceptionListMinimalSchemaMock().list_id }&namespace_type=single&include_expired_exceptions=true` ) .set('kbn-xsrf', 'true') From bdbfd0321359f5f3628ce2ca4a17b7aee4ab10d9 Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Wed, 6 Nov 2024 13:44:27 -0400 Subject: [PATCH 31/53] [Discover] Add support for custom profile context object (#198637) ## Summary This PR adds support for customizing the context object returned by a profile provider's `resolve` method, and accessing it within extension point implementations. The primary use case for this functionality is passing custom dependencies, or async initializing services, e.g. initializing a custom state store and passing it to a context provider in `getRenderAppWrapper`. Flaky test runs: - x25: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7343 ### Checklist - [ ] 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] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] 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 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) - [ ] This will appear in the **Release Notes** and follow the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../public/context_awareness/README.md | 57 ++++++++- .../composable_profile.test.ts | 10 +- .../context_awareness/composable_profile.ts | 33 +++++- .../hooks/use_profile_accessor.test.ts | 4 +- .../hooks/use_profiles.test.tsx | 75 +++++++++--- .../log_document_profile/profile.test.ts | 11 +- .../logs_data_source_profile/profile.test.ts | 23 +++- .../sub_profiles/apache_error_logs.test.ts | 4 +- .../sub_profiles/aws_s3access_logs.test.ts | 4 +- .../kubernetes_container_logs.test.ts | 4 +- .../sub_profiles/nginx_access_logs.test.ts | 4 +- .../sub_profiles/nginx_error_logs.test.ts | 4 +- .../sub_profiles/system_logs.test.ts | 4 +- .../sub_profiles/windows_logs.test.ts | 4 +- .../example_data_source_profile/profile.tsx | 54 +++++---- .../extend_profile_provider.ts | 2 +- .../register_profile_providers.test.ts | 35 ++++-- .../register_profile_providers.ts | 4 +- .../context_awareness/profile_service.test.ts | 53 ++++++--- .../context_awareness/profile_service.ts | 108 +++++++++++++----- .../profiles/data_source_profile.ts | 10 +- .../profiles/document_profile.ts | 10 +- .../profiles/root_profile.ts | 10 +- .../profiles_manager.test.ts | 50 +++++--- .../context_awareness/profiles_manager.ts | 44 ++++--- .../context_awareness/_data_source_profile.ts | 71 ++++++++++++ .../context_awareness/_data_source_profile.ts | 71 ++++++++++++ 27 files changed, 587 insertions(+), 176 deletions(-) diff --git a/src/plugins/discover/public/context_awareness/README.md b/src/plugins/discover/public/context_awareness/README.md index a6e7e3e24a585..31fa5ff9bfcda 100644 --- a/src/plugins/discover/public/context_awareness/README.md +++ b/src/plugins/discover/public/context_awareness/README.md @@ -44,6 +44,8 @@ The merging order for profiles is based on the context level hierarchy (`root` > The following diagram illustrates the extension point merging process: ![image](./docs/merged_accessors.png) +Additionally, extension point implementations are passed an `accessorParams` argument as the second argument after `prev`. This object contains additional parameters that may be useful to extension point implementations, primarily the current `context` object. This is most useful in situations where consumers want to [customize the `context` object](#custom-context-objects) with properties specific to their profile, such as state stores and asynchronously initialized services. + Definitions for composable profiles and the merging routine are located in the [`composable_profile.ts`](./composable_profile.ts) file. ### Supporting services @@ -266,7 +268,8 @@ export const createSecurityRootProfileProvider = (): RootProfileProvider => ({ getCellRenderers: (prev) => (params) => ({ ...prev(params), foo: function FooComponent() { - // Since the app wrapper implementation wrapped Discover with a React context provider, we can now access its values from within our extension point implementations + // Since the app wrapper implementation wrapped Discover with a React context provider, + // we can now access its values from within our extension point implementations const { setFlyoutOpen } = useContext(flyoutContext); return ; @@ -286,6 +289,51 @@ export const createSecurityRootProfileProvider = (): RootProfileProvider => ({ }); ``` +## Custom `context` objects + +By default the `context` object returned from each profile provider's `resolve` method conforms to a standard interface specific to their profile's context level. However, in some situations it may be useful for consumers to extend this object with properties specific to their profile implementation. To support this, profile providers can define a strongly typed `context` interface that extends the default interface, and allows passing properties through to their profile's extension point implementations. One potential use case for this is instantiating state stores or asynchronously initialized services, then accessing them within a `getRenderAppWrapper` implementation to pass to a React context provider: + +```tsx +// The profile provider interfaces accept a custom context object type param +type SecurityRootProfileProvider = RootProfileProvider<{ stateStore: SecurityStateStore }>; + +export const createSecurityRootProfileProvider = ( + services: ProfileProviderServices +): SecurityRootProfileProvider => ({ + profileId: 'security-root-profile', + profile: { + getRenderAppWrapper: + (PrevWrapper, { context }) => + ({ children }) => + ( + + // Custom props can be accessed from the context object available in `accessorParams` + + {children} + + + ), + }, + resolve: async (params) => { + if (params.solutionNavId !== SolutionType.Security) { + return { isMatch: false }; + } + + // Perform async service initialization within the `resolve` method + const stateStore = await initializeSecurityStateStore(services); + + return { + isMatch: true, + context: { + solutionType: SolutionType.Security, + // Include the custom service in the returned context object + stateStore, + }, + }; + }, +}); +``` + ## Overriding defaults Discover ships with a set of common contextual profiles, shared across Solutions in Kibana (e.g. the current logs data source profile). The goal of these profiles is to provide Solution agnostic contextual features to help improve the default data exploration experience for various data types. They should be generally useful across user types and not be tailored to specific Solution workflows – for example, viewing logs should be a delightful experience regardless of whether it’s done within the Observability Solution, the Search Solution, or the classic on-prem experience. @@ -335,9 +383,12 @@ export const createSecurityLogsDataSourceProfileProivder = ( // Completely remove a specific extension point implementation getDocViewer: undefined, // Modify the result of an existing extension point implementation - getCellRenderers: (prev) => (params) => { + getCellRenderers: (prev, accessorParams) => (params) => { // Retrieve and execute the base implementation - const baseImpl = logsDataSourceProfileProvider.profile.getCellRenderers?.(prev); + const baseImpl = logsDataSourceProfileProvider.profile.getCellRenderers?.( + prev, + accessorParams + ); const baseRenderers = baseImpl?.(params); // Return the modified result diff --git a/src/plugins/discover/public/context_awareness/composable_profile.test.ts b/src/plugins/discover/public/context_awareness/composable_profile.test.ts index 34cf44449f20e..bc6a2471c7127 100644 --- a/src/plugins/discover/public/context_awareness/composable_profile.test.ts +++ b/src/plugins/discover/public/context_awareness/composable_profile.test.ts @@ -8,7 +8,7 @@ */ import { DataGridDensity } from '@kbn/unified-data-table'; -import { ComposableProfile, getMergedAccessor } from './composable_profile'; +import { AppliedProfile, getMergedAccessor } from './composable_profile'; import { Profile } from './types'; import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; @@ -30,13 +30,13 @@ describe('getMergedAccessor', () => { it('should merge the accessors in the correct order', () => { const baseImpl: Profile['getCellRenderers'] = jest.fn(() => ({ base: jest.fn() })); - const profile1: ComposableProfile = { + const profile1: AppliedProfile = { getCellRenderers: jest.fn((prev) => (params) => ({ ...prev(params), profile1: jest.fn(), })), }; - const profile2: ComposableProfile = { + const profile2: AppliedProfile = { getCellRenderers: jest.fn((prev) => (params) => ({ ...prev(params), profile2: jest.fn(), @@ -57,10 +57,10 @@ describe('getMergedAccessor', () => { it('should allow overwriting previous accessors', () => { const baseImpl: Profile['getCellRenderers'] = jest.fn(() => ({ base: jest.fn() })); - const profile1: ComposableProfile = { + const profile1: AppliedProfile = { getCellRenderers: jest.fn(() => () => ({ profile1: jest.fn() })), }; - const profile2: ComposableProfile = { + const profile2: AppliedProfile = { getCellRenderers: jest.fn((prev) => (params) => ({ ...prev(params), profile2: jest.fn(), diff --git a/src/plugins/discover/public/context_awareness/composable_profile.ts b/src/plugins/discover/public/context_awareness/composable_profile.ts index 84c8b6afca0b3..4779ad724ab2f 100644 --- a/src/plugins/discover/public/context_awareness/composable_profile.ts +++ b/src/plugins/discover/public/context_awareness/composable_profile.ts @@ -14,16 +14,41 @@ import type { Profile } from './types'; */ export type PartialProfile = Partial; +/** + * The parameters passed to a composable accessor, such as the current context object + */ +export interface ComposableAccessorParams { + /** + * The current context object + */ + context: TContext; +} + /** * An accessor function that allows retrieving the extension point result from previous profiles */ -export type ComposableAccessor = (getPrevious: T) => T; +type ComposableAccessor = ( + prev: TPrev, + params: ComposableAccessorParams +) => TPrev; /** * A partial profile implementation that supports composition across multiple profiles */ -export type ComposableProfile = { - [TKey in keyof TProfile]?: ComposableAccessor; +export type ComposableProfile = { + [TKey in keyof TProfile]?: ComposableAccessor; +}; + +/** + * A partially applied accessor function with parameters bound to a specific context + */ +type AppliedAccessor = (prev: TPrev) => TPrev; + +/** + * A partial profile implementation with applied accessors + */ +export type AppliedProfile = { + [TKey in keyof Profile]?: AppliedAccessor; }; /** @@ -34,7 +59,7 @@ export type ComposableProfile = { * @returns The merged extension point accessor function */ export const getMergedAccessor = ( - profiles: ComposableProfile[], + profiles: AppliedProfile[], key: TKey, baseImpl: Profile[TKey] ) => { diff --git a/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts b/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts index 3fe3c0387149e..65f6f7fb3f30a 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts +++ b/src/plugins/discover/public/context_awareness/hooks/use_profile_accessor.test.ts @@ -8,14 +8,14 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { ComposableProfile, getMergedAccessor } from '../composable_profile'; +import { AppliedProfile, getMergedAccessor } from '../composable_profile'; import { useProfileAccessor } from './use_profile_accessor'; import { getDataTableRecords } from '../../__fixtures__/real_hits'; import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { useProfiles } from './use_profiles'; import { DataGridDensity } from '@kbn/unified-data-table'; -let mockProfiles: ComposableProfile[] = []; +let mockProfiles: AppliedProfile[] = []; jest.mock('./use_profiles', () => ({ useProfiles: jest.fn(() => mockProfiles), diff --git a/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx b/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx index 014d29321bf87..b42fc1c4b3c49 100644 --- a/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx +++ b/src/plugins/discover/public/context_awareness/hooks/use_profiles.test.tsx @@ -8,24 +8,43 @@ */ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { renderHook } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react-hooks'; import React from 'react'; import { discoverServiceMock } from '../../__mocks__/services'; -import { GetProfilesOptions } from '../profiles_manager'; +import type { GetProfilesOptions } from '../profiles_manager'; import { createContextAwarenessMocks } from '../__mocks__'; import { useProfiles } from './use_profiles'; +import type { CellRenderersExtensionParams } from '../types'; +import type { AppliedProfile } from '../composable_profile'; +import { SolutionType } from '../profiles'; const { rootProfileProviderMock, dataSourceProfileProviderMock, documentProfileProviderMock, + rootProfileServiceMock, + dataSourceProfileServiceMock, + documentProfileServiceMock, contextRecordMock, contextRecordMock2, profilesManagerMock, -} = createContextAwarenessMocks(); +} = createContextAwarenessMocks({ shouldRegisterProviders: false }); -profilesManagerMock.resolveRootProfile({}); -profilesManagerMock.resolveDataSourceProfile({}); +rootProfileServiceMock.registerProvider({ + profileId: 'other-root-profile', + profile: {}, + resolve: (params) => { + if (params.solutionNavId === 'test') { + return { isMatch: true, context: { solutionType: SolutionType.Default } }; + } + + return { isMatch: false }; + }, +}); + +rootProfileServiceMock.registerProvider(rootProfileProviderMock); +dataSourceProfileServiceMock.registerProvider(dataSourceProfileProviderMock); +documentProfileServiceMock.registerProvider(documentProfileProviderMock); const record = profilesManagerMock.resolveDocumentProfile({ record: contextRecordMock }); const record2 = profilesManagerMock.resolveDocumentProfile({ record: contextRecordMock2 }); @@ -45,22 +64,30 @@ const render = () => { }; describe('useProfiles', () => { - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); + await profilesManagerMock.resolveRootProfile({}); + await profilesManagerMock.resolveDataSourceProfile({}); }); it('should return profiles', () => { const { result } = render(); expect(getProfilesSpy).toHaveBeenCalledTimes(2); expect(getProfiles$Spy).toHaveBeenCalledTimes(1); - expect(result.current).toEqual([ - rootProfileProviderMock.profile, - dataSourceProfileProviderMock.profile, - documentProfileProviderMock.profile, - ]); + expect(result.current).toHaveLength(3); + const [rootProfile, dataSourceProfile, documentProfile] = result.current; + const baseImpl = () => ({}); + rootProfile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(rootProfileProviderMock.profile.getCellRenderers).toHaveBeenCalledTimes(1); + dataSourceProfile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(dataSourceProfileProviderMock.profile.getCellRenderers).toHaveBeenCalledTimes(1); + documentProfile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect( + (documentProfileProviderMock.profile as AppliedProfile).getCellRenderers + ).toHaveBeenCalledTimes(1); }); - it('should return the same array reference if profiles do not change', () => { + it('should return the same array reference if profiles and record do not change', () => { const { result, rerender } = render(); expect(getProfilesSpy).toHaveBeenCalledTimes(2); expect(getProfiles$Spy).toHaveBeenCalledTimes(1); @@ -69,13 +96,23 @@ describe('useProfiles', () => { expect(getProfilesSpy).toHaveBeenCalledTimes(2); expect(getProfiles$Spy).toHaveBeenCalledTimes(1); expect(result.current).toBe(prevResult); + }); + + it('should return a different array reference if record changes', () => { + const { result, rerender } = render(); + expect(getProfilesSpy).toHaveBeenCalledTimes(2); + expect(getProfiles$Spy).toHaveBeenCalledTimes(1); + const prevResult = result.current; rerender({ record: record2 }); expect(getProfilesSpy).toHaveBeenCalledTimes(3); expect(getProfiles$Spy).toHaveBeenCalledTimes(2); - expect(result.current).toBe(prevResult); + expect(result.current).not.toBe(prevResult); + expect(result.current[0]).toBe(prevResult[0]); + expect(result.current[1]).toBe(prevResult[1]); + expect(result.current[2]).not.toBe(prevResult[2]); }); - it('should return a different array reference if profiles change', () => { + it('should return a different array reference if profiles change', async () => { const { result, rerender } = render(); expect(getProfilesSpy).toHaveBeenCalledTimes(2); expect(getProfiles$Spy).toHaveBeenCalledTimes(1); @@ -84,9 +121,15 @@ describe('useProfiles', () => { expect(getProfilesSpy).toHaveBeenCalledTimes(2); expect(getProfiles$Spy).toHaveBeenCalledTimes(1); expect(result.current).toBe(prevResult); - rerender({ record: undefined }); + await act(async () => { + await profilesManagerMock.resolveRootProfile({ solutionNavId: 'test' }); + }); + rerender({ record }); expect(getProfilesSpy).toHaveBeenCalledTimes(3); - expect(getProfiles$Spy).toHaveBeenCalledTimes(2); + expect(getProfiles$Spy).toHaveBeenCalledTimes(1); expect(result.current).not.toBe(prevResult); + expect(result.current[0]).not.toBe(prevResult[0]); + expect(result.current[1]).toBe(prevResult[1]); + expect(result.current[2]).not.toBe(prevResult[2]); }); }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/log_document_profile/profile.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/log_document_profile/profile.test.ts index 87c5ac137380e..394ccdf71da13 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/log_document_profile/profile.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/log_document_profile/profile.test.ts @@ -101,10 +101,13 @@ describe('logDocumentProfileProvider', () => { describe('getDocViewer', () => { it('adds a log overview doc view to the registry', () => { - const getDocViewer = logDocumentProfileProvider.profile.getDocViewer!(() => ({ - title: 'test title', - docViewsRegistry: (registry) => registry, - })); + const getDocViewer = logDocumentProfileProvider.profile.getDocViewer!( + () => ({ + title: 'test title', + docViewsRegistry: (registry) => registry, + }), + { context: { type: DocumentType.Log } } + ); const docViewer = getDocViewer({ record: buildDataTableRecord({}), }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.test.ts index 861ef0b590224..9e8f661a61a33 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/profile.test.ts @@ -117,7 +117,9 @@ describe('logsDataSourceProfileProvider', () => { const row = buildDataTableRecord({ fields: { 'log.level': 'info' } }); const euiTheme = { euiTheme: { colors: {} } } as unknown as EuiThemeComputed; const getRowIndicatorProvider = - logsDataSourceProfileProvider.profile.getRowIndicatorProvider?.(() => undefined); + logsDataSourceProfileProvider.profile.getRowIndicatorProvider?.(() => undefined, { + context: { category: DataSourceCategory.Logs }, + }); const getRowIndicator = getRowIndicatorProvider?.({ dataView: dataViewWithLogLevel, }); @@ -130,7 +132,9 @@ describe('logsDataSourceProfileProvider', () => { const row = buildDataTableRecord({ fields: { other: 'info' } }); const euiTheme = { euiTheme: { colors: {} } } as unknown as EuiThemeComputed; const getRowIndicatorProvider = - logsDataSourceProfileProvider.profile.getRowIndicatorProvider?.(() => undefined); + logsDataSourceProfileProvider.profile.getRowIndicatorProvider?.(() => undefined, { + context: { category: DataSourceCategory.Logs }, + }); const getRowIndicator = getRowIndicatorProvider?.({ dataView: dataViewWithLogLevel, }); @@ -141,7 +145,9 @@ describe('logsDataSourceProfileProvider', () => { it('should not set the color indicator handler if data view does not have log level field', () => { const getRowIndicatorProvider = - logsDataSourceProfileProvider.profile.getRowIndicatorProvider?.(() => undefined); + logsDataSourceProfileProvider.profile.getRowIndicatorProvider?.(() => undefined, { + context: { category: DataSourceCategory.Logs }, + }); const getRowIndicator = getRowIndicatorProvider?.({ dataView: dataViewWithoutLogLevel, }); @@ -152,7 +158,12 @@ describe('logsDataSourceProfileProvider', () => { describe('getCellRenderers', () => { it('should return cell renderers for log level fields', () => { - const getCellRenderers = logsDataSourceProfileProvider.profile.getCellRenderers?.(() => ({})); + const getCellRenderers = logsDataSourceProfileProvider.profile.getCellRenderers?.( + () => ({}), + { + context: { category: DataSourceCategory.Logs }, + } + ); const getCellRenderersParams = { actions: { addFilter: jest.fn() }, dataView: dataViewWithTimefieldMock, @@ -172,7 +183,9 @@ describe('logsDataSourceProfileProvider', () => { describe('getRowAdditionalLeadingControls', () => { it('should return the passed additional controls', () => { const getRowAdditionalLeadingControls = - logsDataSourceProfileProvider.profile.getRowAdditionalLeadingControls?.(() => undefined); + logsDataSourceProfileProvider.profile.getRowAdditionalLeadingControls?.(() => undefined, { + context: { category: DataSourceCategory.Logs }, + }); const rowAdditionalLeadingControls = getRowAdditionalLeadingControls?.({ dataView: dataViewWithLogLevel, }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/apache_error_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/apache_error_logs.test.ts index 9b3e64e520be3..bda23309baec3 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/apache_error_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/apache_error_logs.test.ts @@ -41,7 +41,9 @@ describe('createApacheErrorLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.ts index fc97e67b1bee7..c95cc090195a2 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.ts @@ -41,7 +41,9 @@ describe('createAwsS3accessLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.ts index 301ef9ca52a86..3f43aa3b6808a 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.ts @@ -41,7 +41,9 @@ describe('createKubernetesContainerLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_access_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_access_logs.test.ts index 0265c17152177..3116ebd55d3e7 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_access_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_access_logs.test.ts @@ -41,7 +41,9 @@ describe('createNginxAccessLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_error_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_error_logs.test.ts index 7ce8e49337a51..c5a980c31d21e 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_error_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/nginx_error_logs.test.ts @@ -41,7 +41,9 @@ describe('createNginxErrorLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/system_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/system_logs.test.ts index 760546b89bc51..f6105d9115fb4 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/system_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/system_logs.test.ts @@ -41,7 +41,9 @@ describe('createSystemLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/windows_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/windows_logs.test.ts index ce144b9167646..b54cdf3787f5a 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/windows_logs.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/common/logs_data_source_profile/sub_profiles/windows_logs.test.ts @@ -41,7 +41,9 @@ describe('createWindowsLogsDataSourceProfileProvider', () => { }); it('should return default app state', () => { - const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({}), { + context: { category: DataSourceCategory.Logs }, + }); expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ columns: [ { name: 'timestamp', width: 212 }, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx index b32267c0b3fe8..1fe833ba99afe 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/example/example_data_source_profile/profile.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EuiBadge, EuiLink, EuiFlyout } from '@elastic/eui'; +import { EuiBadge, EuiLink, EuiFlyout, EuiFlyoutBody } from '@elastic/eui'; import { AppMenuActionId, AppMenuActionType, @@ -23,7 +23,9 @@ import { DataSourceType, isDataSourceType } from '../../../../../common/data_sou import { DataSourceCategory, DataSourceProfileProvider } from '../../../profiles'; import { useExampleContext } from '../example_context'; -export const createExampleDataSourceProfileProvider = (): DataSourceProfileProvider => ({ +export const createExampleDataSourceProfileProvider = (): DataSourceProfileProvider<{ + formatRecord: (flattenedRecord: Record) => string; +}> => ({ profileId: 'example-data-source-profile', isExperimental: true, profile: { @@ -74,25 +76,32 @@ export const createExampleDataSourceProfileProvider = (): DataSourceProfileProvi ); }, }), - getDocViewer: (prev) => (params) => { - const recordId = params.record.id; - const prevValue = prev(params); - return { - title: `Record #${recordId}`, - docViewsRegistry: (registry) => { - registry.add({ - id: 'doc_view_example', - title: 'Example', - order: 0, - component: () => ( -
Example Doc View
- ), - }); + getDocViewer: + (prev, { context }) => + (params) => { + const recordId = params.record.id; + const prevValue = prev(params); + return { + title: `Record #${recordId}`, + docViewsRegistry: (registry) => { + registry.add({ + id: 'doc_view_example', + title: 'Example', + order: 0, + component: () => ( + +
Example Doc View
+
+                    {context.formatRecord(params.record.flattened)}
+                  
+
+ ), + }); - return prevValue.docViewsRegistry(registry); - }, - }; - }, + return prevValue.docViewsRegistry(registry); + }, + }; + }, /** * The `getAppMenu` extension point gives access to AppMenuRegistry with methods registerCustomAction and registerCustomActionUnderSubmenu. * The extension also provides the essential params like current dataView, adHocDataViews etc when defining a custom action implementation. @@ -267,7 +276,10 @@ export const createExampleDataSourceProfileProvider = (): DataSourceProfileProvi return { isMatch: true, - context: { category: DataSourceCategory.Logs }, + context: { + category: DataSourceCategory.Logs, + formatRecord: (record) => JSON.stringify(record, null, 2), + }, }; }, }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts index 9382101464ec7..9eea569b66217 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts @@ -15,7 +15,7 @@ import type { BaseProfileProvider } from '../profile_service'; * @param extension The extension to apply to the base profile provider * @returns The extended profile provider */ -export const extendProfileProvider = >( +export const extendProfileProvider = >( baseProvider: TProvider, extension: Partial & Pick ): TProvider => ({ diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts index ddff243b5117a..69edc5d3b7cd3 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts @@ -12,17 +12,21 @@ import { createContextAwarenessMocks } from '../__mocks__'; import { createExampleRootProfileProvider } from './example/example_root_profile'; import { createExampleDataSourceProfileProvider } from './example/example_data_source_profile/profile'; import { createExampleDocumentProfileProvider } from './example/example_document_profile'; - import { registerProfileProviders, registerEnabledProfileProviders, } from './register_profile_providers'; +import type { CellRenderersExtensionParams } from '../types'; const exampleRootProfileProvider = createExampleRootProfileProvider(); const exampleDataSourceProfileProvider = createExampleDataSourceProfileProvider(); const exampleDocumentProfileProvider = createExampleDocumentProfileProvider(); describe('registerEnabledProfileProviders', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should register all profile providers', async () => { const { rootProfileServiceMock, rootProfileProviderMock } = createContextAwarenessMocks({ shouldRegisterProviders: false, @@ -33,37 +37,52 @@ describe('registerEnabledProfileProviders', () => { enabledExperimentalProfileIds: [], }); const context = await rootProfileServiceMock.resolve({ solutionNavId: null }); - expect(rootProfileServiceMock.getProfile(context)).toBe(rootProfileProviderMock.profile); + const profile = rootProfileServiceMock.getProfile({ context }); + const baseImpl = () => ({}); + profile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(rootProfileProviderMock.profile.getCellRenderers).toHaveBeenCalledTimes(1); + expect(rootProfileProviderMock.profile.getCellRenderers).toHaveBeenCalledWith(baseImpl, { + context, + }); }); it('should not register experimental profile providers by default', async () => { + jest.spyOn(exampleRootProfileProvider.profile, 'getCellRenderers'); const { rootProfileServiceMock } = createContextAwarenessMocks({ shouldRegisterProviders: false, }); - registerEnabledProfileProviders({ profileService: rootProfileServiceMock, providers: [exampleRootProfileProvider], enabledExperimentalProfileIds: [], }); const context = await rootProfileServiceMock.resolve({ solutionNavId: null }); - expect(rootProfileServiceMock.getProfile(context)).not.toBe(exampleRootProfileProvider.profile); - expect(rootProfileServiceMock.getProfile(context)).toMatchObject({}); + const profile = rootProfileServiceMock.getProfile({ context }); + const baseImpl = () => ({}); + profile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(exampleRootProfileProvider.profile.getCellRenderers).not.toHaveBeenCalled(); + expect(profile).toMatchObject({}); }); it('should register experimental profile providers when enabled by config', async () => { + jest.spyOn(exampleRootProfileProvider.profile, 'getCellRenderers'); const { rootProfileServiceMock, rootProfileProviderMock } = createContextAwarenessMocks({ shouldRegisterProviders: false, }); - registerEnabledProfileProviders({ profileService: rootProfileServiceMock, providers: [exampleRootProfileProvider], enabledExperimentalProfileIds: [exampleRootProfileProvider.profileId], }); const context = await rootProfileServiceMock.resolve({ solutionNavId: null }); - expect(rootProfileServiceMock.getProfile(context)).toBe(exampleRootProfileProvider.profile); - expect(rootProfileServiceMock.getProfile(context)).not.toBe(rootProfileProviderMock.profile); + const profile = rootProfileServiceMock.getProfile({ context }); + const baseImpl = () => ({}); + profile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(exampleRootProfileProvider.profile.getCellRenderers).toHaveBeenCalledTimes(1); + expect(exampleRootProfileProvider.profile.getCellRenderers).toHaveBeenCalledWith(baseImpl, { + context, + }); + expect(rootProfileProviderMock.profile.getCellRenderers).not.toHaveBeenCalled(); }); }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts index 5bac0d9cea483..997edac1bae57 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -86,8 +86,8 @@ export const registerProfileProviders = async ({ * @param options Register enabled profile providers options */ export const registerEnabledProfileProviders = < - TProvider extends BaseProfileProvider<{}>, - TService extends BaseProfileService + TProvider extends BaseProfileProvider<{}, {}>, + TService extends BaseProfileService >({ profileService, providers: availableProviders, diff --git a/src/plugins/discover/public/context_awareness/profile_service.test.ts b/src/plugins/discover/public/context_awareness/profile_service.test.ts index 22190dcb60da2..20ef2ba4556ae 100644 --- a/src/plugins/discover/public/context_awareness/profile_service.test.ts +++ b/src/plugins/discover/public/context_awareness/profile_service.test.ts @@ -9,8 +9,14 @@ /* eslint-disable max-classes-per-file */ -import { AsyncProfileService, ContextWithProfileId, ProfileService } from './profile_service'; -import { Profile } from './types'; +import { + AsyncProfileProvider, + AsyncProfileService, + ContextWithProfileId, + ProfileProvider, + ProfileService, +} from './profile_service'; +import type { CellRenderersExtensionParams, Profile } from './types'; interface TestParams { myParam: string; @@ -25,7 +31,7 @@ const defaultContext: ContextWithProfileId = { myContext: 'test', }; -class TestProfileService extends ProfileService { +class TestProfileService extends ProfileService> { constructor() { super(defaultContext); } @@ -33,7 +39,9 @@ class TestProfileService extends ProfileService[0]; -class TestAsyncProfileService extends AsyncProfileService { +class TestAsyncProfileService extends AsyncProfileService< + AsyncProfileProvider +> { constructor() { super(defaultContext); } @@ -43,25 +51,27 @@ type TestAsyncProfileProvider = Parameters (params) => prev(params)), + }, resolve: jest.fn(() => ({ isMatch: false })), }; const provider2: TestProfileProvider = { profileId: 'test-profile-2', - profile: { getCellRenderers: jest.fn() }, + profile: { getCellRenderers: jest.fn((prev) => (params) => prev(params)) }, resolve: jest.fn(({ myParam }) => ({ isMatch: true, context: { myContext: myParam } })), }; const provider3: TestProfileProvider = { profileId: 'test-profile-3', - profile: { getCellRenderers: jest.fn() }, + profile: { getCellRenderers: jest.fn((prev) => (params) => prev(params)) }, resolve: jest.fn(({ myParam }) => ({ isMatch: true, context: { myContext: myParam } })), }; const asyncProvider2: TestAsyncProfileProvider = { profileId: 'test-profile-2', - profile: { getCellRenderers: jest.fn() }, + profile: { getCellRenderers: jest.fn((prev) => (params) => prev(params)) }, resolve: jest.fn(async ({ myParam }) => ({ isMatch: true, context: { myContext: myParam } })), }; @@ -80,17 +90,30 @@ describe('ProfileService', () => { it('should allow registering providers and getting profiles', () => { service.registerProvider(provider); service.registerProvider(provider2); - expect(service.getProfile({ profileId: 'test-profile-1', myContext: 'test' })).toBe( - provider.profile - ); - expect(service.getProfile({ profileId: 'test-profile-2', myContext: 'test' })).toBe( - provider2.profile - ); + const params = { + context: { profileId: 'test-profile-1', myContext: 'test' }, + }; + const params2 = { + context: { profileId: 'test-profile-2', myContext: 'test' }, + }; + const profile = service.getProfile(params); + const profile2 = service.getProfile(params2); + const baseImpl = jest.fn(() => ({})); + profile.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(provider.profile.getCellRenderers).toHaveBeenCalledTimes(1); + expect(provider.profile.getCellRenderers).toHaveBeenCalledWith(baseImpl, params); + expect(baseImpl).toHaveBeenCalledTimes(1); + profile2.getCellRenderers?.(baseImpl)({} as unknown as CellRenderersExtensionParams); + expect(provider2.profile.getCellRenderers).toHaveBeenCalledTimes(1); + expect(provider2.profile.getCellRenderers).toHaveBeenCalledWith(baseImpl, params2); + expect(baseImpl).toHaveBeenCalledTimes(2); }); it('should return empty profile if no provider is found', () => { service.registerProvider(provider); - expect(service.getProfile({ profileId: 'test-profile-2', myContext: 'test' })).toEqual({}); + expect( + service.getProfile({ context: { profileId: 'test-profile-2', myContext: 'test' } }) + ).toEqual({}); }); it('should resolve to first matching context', () => { diff --git a/src/plugins/discover/public/context_awareness/profile_service.ts b/src/plugins/discover/public/context_awareness/profile_service.ts index aaeb7664aa5cd..e520b42083f1e 100644 --- a/src/plugins/discover/public/context_awareness/profile_service.ts +++ b/src/plugins/discover/public/context_awareness/profile_service.ts @@ -9,13 +9,18 @@ /* eslint-disable max-classes-per-file */ -import type { ComposableProfile, PartialProfile } from './composable_profile'; -import type { Profile } from './types'; +import { isFunction } from 'lodash'; +import type { + AppliedProfile, + ComposableAccessorParams, + ComposableProfile, + PartialProfile, +} from './composable_profile'; /** * The profile provider resolution result */ -export type ResolveProfileResult = +type ResolveProfileResult = | { /** * `true` if the associated profile is a match @@ -33,15 +38,10 @@ export type ResolveProfileResult = isMatch: false; }; -/** - * Context object with an injected profile ID - */ -export type ContextWithProfileId = TContext & { profileId: string }; - /** * The base profile provider interface */ -export interface BaseProfileProvider { +export interface BaseProfileProvider { /** * The unique profile ID */ @@ -49,7 +49,7 @@ export interface BaseProfileProvider { /** * The composable profile implementation */ - profile: ComposableProfile; + profile: ComposableProfile; /** * Set the `isExperimental` flag to `true` for any profile which is under development and should not be enabled by default. * @@ -68,7 +68,7 @@ export interface BaseProfileProvider { * A synchronous profile provider interface */ export interface ProfileProvider - extends BaseProfileProvider { + extends BaseProfileProvider { /** * The method responsible for context resolution and determining if the associated profile is a match * @param params Parameters specific to the provider context level @@ -81,7 +81,7 @@ export interface ProfileProvider - extends BaseProfileProvider { + extends BaseProfileProvider { /** * The method responsible for context resolution and determining if the associated profile is a match * @param params Parameters specific to the provider context level @@ -92,12 +92,36 @@ export interface AsyncProfileProvider ResolveProfileResult | Promise>; } +/** + * Context object with an injected profile ID + */ +export type ContextWithProfileId = TContext & + Pick, 'profileId'>; + +/** + * Used to extract the profile type from a profile provider + */ +type ExtractProfile = TProvider extends BaseProfileProvider + ? TProfile + : never; + +/** + * Used to extract the context type from a profile provider + */ +type ExtractContext = TProvider extends BaseProfileProvider<{}, infer TContext> + ? TContext + : never; + const EMPTY_PROFILE = {}; /** * The base profile service implementation */ -export abstract class BaseProfileService, TContext> { +export abstract class BaseProfileService< + TProvider extends BaseProfileProvider, + TProfile extends PartialProfile = ExtractProfile, + TContext = ExtractContext +> { protected readonly providers: TProvider[] = []; /** @@ -114,31 +138,59 @@ export abstract class BaseProfileService): ComposableProfile { - const provider = this.providers.find((current) => current.profileId === context.profileId); - return provider?.profile ?? EMPTY_PROFILE; + public getProfile( + params: ComposableAccessorParams> + ): AppliedProfile { + const provider = this.providers.find( + (current) => current.profileId === params.context.profileId + ); + + if (!provider?.profile) { + return EMPTY_PROFILE; + } + + return new Proxy(provider.profile, { + get: (target, prop, receiver) => { + const accessor = Reflect.get(target, prop, receiver); + + if (!isFunction(accessor)) { + return accessor; + } + + return (prev: Parameters[0]) => accessor(prev, params); + }, + }) as AppliedProfile; } } +/** + * Used to extract the parameters type from a profile provider + */ +type ExtractParams = TProvider extends ProfileProvider<{}, infer P, {}> + ? P + : TProvider extends AsyncProfileProvider<{}, infer P, {}> + ? P + : never; + /** * A synchronous profile service implementation */ export class ProfileService< - TProfile extends PartialProfile, - TParams, - TContext -> extends BaseProfileService, TContext> { + TProvider extends ProfileProvider<{}, TParams, TContext>, + TParams = ExtractParams, + TContext = ExtractContext +> extends BaseProfileService { /** * Performs context resolution based on the provided context level parameters, * returning the resolved context from the first matching profile provider * @param params Parameters specific to the service context level * @returns The resolved context object with an injected profile ID */ - public resolve(params: TParams) { + public resolve(params: TParams): ContextWithProfileId { for (const provider of this.providers) { const result = provider.resolve(params); @@ -158,17 +210,17 @@ export class ProfileService< * An asynchronous profile service implementation */ export class AsyncProfileService< - TProfile extends PartialProfile, - TParams, - TContext -> extends BaseProfileService, TContext> { + TProvider extends AsyncProfileProvider<{}, TParams, TContext>, + TParams = ExtractParams, + TContext = ExtractContext +> extends BaseProfileService { /** * Performs context resolution based on the provided context level parameters, * returning the resolved context from the first matching profile provider * @param params Parameters specific to the service context level * @returns The resolved context object with an injected profile ID */ - public async resolve(params: TParams) { + public async resolve(params: TParams): Promise> { for (const provider of this.providers) { const result = await provider.resolve(params); diff --git a/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts b/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts index c4d06e0a502cb..7f3933de185a6 100644 --- a/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts @@ -59,17 +59,13 @@ export interface DataSourceContext { category: DataSourceCategory; } -export type DataSourceProfileProvider = AsyncProfileProvider< +export type DataSourceProfileProvider = AsyncProfileProvider< DataSourceProfile, DataSourceProfileProviderParams, - DataSourceContext + DataSourceContext & TProviderContext >; -export class DataSourceProfileService extends AsyncProfileService< - DataSourceProfile, - DataSourceProfileProviderParams, - DataSourceContext -> { +export class DataSourceProfileService extends AsyncProfileService { constructor() { super({ profileId: 'default-data-source-profile', diff --git a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts index cd14e9cdec010..f555dfe8c4292 100644 --- a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts @@ -54,17 +54,13 @@ export interface DocumentContext { type: DocumentType; } -export type DocumentProfileProvider = ProfileProvider< +export type DocumentProfileProvider = ProfileProvider< DocumentProfile, DocumentProfileProviderParams, - DocumentContext + DocumentContext & TProviderContext >; -export class DocumentProfileService extends ProfileService< - DocumentProfile, - DocumentProfileProviderParams, - DocumentContext -> { +export class DocumentProfileService extends ProfileService { constructor() { super({ profileId: 'default-document-profile', diff --git a/src/plugins/discover/public/context_awareness/profiles/root_profile.ts b/src/plugins/discover/public/context_awareness/profiles/root_profile.ts index 853c93c05cf64..0a5909538c498 100644 --- a/src/plugins/discover/public/context_awareness/profiles/root_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/root_profile.ts @@ -45,17 +45,13 @@ export interface RootContext { solutionType: SolutionType; } -export type RootProfileProvider = AsyncProfileProvider< +export type RootProfileProvider = AsyncProfileProvider< RootProfile, RootProfileProviderParams, - RootContext + RootContext & TProviderContext >; -export class RootProfileService extends AsyncProfileService< - RootProfile, - RootProfileProviderParams, - RootContext -> { +export class RootProfileService extends AsyncProfileService { constructor() { super({ profileId: 'default-root-profile', diff --git a/src/plugins/discover/public/context_awareness/profiles_manager.test.ts b/src/plugins/discover/public/context_awareness/profiles_manager.test.ts index da5ad8b56dcf3..4e1608961f7f5 100644 --- a/src/plugins/discover/public/context_awareness/profiles_manager.test.ts +++ b/src/plugins/discover/public/context_awareness/profiles_manager.test.ts @@ -12,11 +12,15 @@ import { createEsqlDataSource } from '../../common/data_sources'; import { addLog } from '../utils/add_log'; import { SolutionType } from './profiles/root_profile'; import { createContextAwarenessMocks } from './__mocks__'; +import type { ComposableProfile } from './composable_profile'; jest.mock('../utils/add_log'); let mocks = createContextAwarenessMocks(); +const toAppliedProfile = (profile: ComposableProfile<{}, {}>) => + Object.keys(profile).reduce((acc, key) => ({ ...acc, [key]: expect.any(Function) }), {}); + describe('ProfilesManager', () => { beforeEach(() => { jest.clearAllMocks(); @@ -32,13 +36,17 @@ describe('ProfilesManager', () => { it('should resolve root profile', async () => { await mocks.profilesManagerMock.resolveRootProfile({}); const profiles = mocks.profilesManagerMock.getProfiles(); - expect(profiles).toEqual([mocks.rootProfileProviderMock.profile, {}, {}]); + expect(profiles).toEqual([toAppliedProfile(mocks.rootProfileProviderMock.profile), {}, {}]); }); it('should resolve data source profile', async () => { await mocks.profilesManagerMock.resolveDataSourceProfile({}); const profiles = mocks.profilesManagerMock.getProfiles(); - expect(profiles).toEqual([{}, mocks.dataSourceProfileProviderMock.profile, {}]); + expect(profiles).toEqual([ + {}, + toAppliedProfile(mocks.dataSourceProfileProviderMock.profile), + {}, + ]); }); it('should resolve document profile', async () => { @@ -46,7 +54,7 @@ describe('ProfilesManager', () => { record: mocks.contextRecordMock, }); const profiles = mocks.profilesManagerMock.getProfiles({ record }); - expect(profiles).toEqual([{}, {}, mocks.documentProfileProviderMock.profile]); + expect(profiles).toEqual([{}, {}, toAppliedProfile(mocks.documentProfileProviderMock.profile)]); }); it('should resolve multiple profiles', async () => { @@ -57,9 +65,9 @@ describe('ProfilesManager', () => { }); const profiles = mocks.profilesManagerMock.getProfiles({ record }); expect(profiles).toEqual([ - mocks.rootProfileProviderMock.profile, - mocks.dataSourceProfileProviderMock.profile, - mocks.documentProfileProviderMock.profile, + toAppliedProfile(mocks.rootProfileProviderMock.profile), + toAppliedProfile(mocks.dataSourceProfileProviderMock.profile), + toAppliedProfile(mocks.documentProfileProviderMock.profile), ]); expect(mocks.ebtManagerMock.updateProfilesContextWith).toHaveBeenCalledWith([ @@ -77,20 +85,24 @@ describe('ProfilesManager', () => { const next = jest.fn(); profiles$.subscribe(next); expect(getProfilesSpy).toHaveBeenCalledTimes(1); - expect(next).toHaveBeenCalledWith([{}, {}, mocks.documentProfileProviderMock.profile]); + expect(next).toHaveBeenCalledWith([ + {}, + {}, + toAppliedProfile(mocks.documentProfileProviderMock.profile), + ]); await mocks.profilesManagerMock.resolveRootProfile({}); expect(getProfilesSpy).toHaveBeenCalledTimes(2); expect(next).toHaveBeenCalledWith([ - mocks.rootProfileProviderMock.profile, + toAppliedProfile(mocks.rootProfileProviderMock.profile), {}, - mocks.documentProfileProviderMock.profile, + toAppliedProfile(mocks.documentProfileProviderMock.profile), ]); await mocks.profilesManagerMock.resolveDataSourceProfile({}); expect(getProfilesSpy).toHaveBeenCalledTimes(3); expect(next).toHaveBeenCalledWith([ - mocks.rootProfileProviderMock.profile, - mocks.dataSourceProfileProviderMock.profile, - mocks.documentProfileProviderMock.profile, + toAppliedProfile(mocks.rootProfileProviderMock.profile), + toAppliedProfile(mocks.dataSourceProfileProviderMock.profile), + toAppliedProfile(mocks.documentProfileProviderMock.profile), ]); }); @@ -135,7 +147,7 @@ describe('ProfilesManager', () => { it('should log an error and fall back to the default profile if root profile resolution fails', async () => { await mocks.profilesManagerMock.resolveRootProfile({ solutionNavId: 'solutionNavId' }); let profiles = mocks.profilesManagerMock.getProfiles(); - expect(profiles).toEqual([mocks.rootProfileProviderMock.profile, {}, {}]); + expect(profiles).toEqual([toAppliedProfile(mocks.rootProfileProviderMock.profile), {}, {}]); const resolveSpy = jest.spyOn(mocks.rootProfileProviderMock, 'resolve'); resolveSpy.mockRejectedValue(new Error('Failed to resolve')); await mocks.profilesManagerMock.resolveRootProfile({ solutionNavId: 'newSolutionNavId' }); @@ -153,7 +165,11 @@ describe('ProfilesManager', () => { query: { esql: 'from *' }, }); let profiles = mocks.profilesManagerMock.getProfiles(); - expect(profiles).toEqual([{}, mocks.dataSourceProfileProviderMock.profile, {}]); + expect(profiles).toEqual([ + {}, + toAppliedProfile(mocks.dataSourceProfileProviderMock.profile), + {}, + ]); const resolveSpy = jest.spyOn(mocks.dataSourceProfileProviderMock, 'resolve'); resolveSpy.mockRejectedValue(new Error('Failed to resolve')); await mocks.profilesManagerMock.resolveDataSourceProfile({ @@ -173,7 +189,7 @@ describe('ProfilesManager', () => { record: mocks.contextRecordMock, }); let profiles = mocks.profilesManagerMock.getProfiles({ record }); - expect(profiles).toEqual([{}, {}, mocks.documentProfileProviderMock.profile]); + expect(profiles).toEqual([{}, {}, toAppliedProfile(mocks.documentProfileProviderMock.profile)]); const resolveSpy = jest.spyOn(mocks.documentProfileProviderMock, 'resolve'); resolveSpy.mockImplementation(() => { throw new Error('Failed to resolve'); @@ -220,7 +236,7 @@ describe('ProfilesManager', () => { resolvedDeferredResult2$.next(undefined); await promise2; expect(mocks.profilesManagerMock.getProfiles()).toEqual([ - mocks.rootProfileProviderMock.profile, + toAppliedProfile(mocks.rootProfileProviderMock.profile), {}, {}, ]); @@ -266,7 +282,7 @@ describe('ProfilesManager', () => { await promise2; expect(mocks.profilesManagerMock.getProfiles()).toEqual([ {}, - mocks.dataSourceProfileProviderMock.profile, + toAppliedProfile(mocks.dataSourceProfileProviderMock.profile), {}, ]); }); diff --git a/src/plugins/discover/public/context_awareness/profiles_manager.ts b/src/plugins/discover/public/context_awareness/profiles_manager.ts index a209e5dfc9f7c..d5714565dacd2 100644 --- a/src/plugins/discover/public/context_awareness/profiles_manager.ts +++ b/src/plugins/discover/public/context_awareness/profiles_manager.ts @@ -10,7 +10,7 @@ import type { DataTableRecord } from '@kbn/discover-utils'; import { isOfAggregateQueryType } from '@kbn/es-query'; import { isEqual } from 'lodash'; -import { BehaviorSubject, combineLatest, map } from 'rxjs'; +import { BehaviorSubject, combineLatest, map, skip } from 'rxjs'; import { DataSourceType, isDataSourceType } from '../../common/data_sources'; import { addLog } from '../utils/add_log'; import type { @@ -26,6 +26,7 @@ import type { } from './profiles'; import type { ContextWithProfileId } from './profile_service'; import type { DiscoverEBTManager } from '../services/discover_ebt_manager'; +import type { AppliedProfile } from './composable_profile'; interface SerializedRootProfileParams { solutionNavId: RootProfileProviderParams['solutionNavId']; @@ -53,8 +54,9 @@ export interface GetProfilesOptions { export class ProfilesManager { private readonly rootContext$: BehaviorSubject>; private readonly dataSourceContext$: BehaviorSubject>; - private readonly ebtManager: DiscoverEBTManager; + private rootProfile: AppliedProfile; + private dataSourceProfile: AppliedProfile; private prevRootProfileParams?: SerializedRootProfileParams; private prevDataSourceProfileParams?: SerializedDataSourceProfileParams; private rootProfileAbortController?: AbortController; @@ -64,11 +66,22 @@ export class ProfilesManager { private readonly rootProfileService: RootProfileService, private readonly dataSourceProfileService: DataSourceProfileService, private readonly documentProfileService: DocumentProfileService, - ebtManager: DiscoverEBTManager + private readonly ebtManager: DiscoverEBTManager ) { this.rootContext$ = new BehaviorSubject(rootProfileService.defaultContext); this.dataSourceContext$ = new BehaviorSubject(dataSourceProfileService.defaultContext); - this.ebtManager = ebtManager; + this.rootProfile = rootProfileService.getProfile({ context: this.rootContext$.getValue() }); + this.dataSourceProfile = dataSourceProfileService.getProfile({ + context: this.dataSourceContext$.getValue(), + }); + + this.rootContext$.pipe(skip(1)).subscribe((context) => { + this.rootProfile = rootProfileService.getProfile({ context }); + }); + + this.dataSourceContext$.pipe(skip(1)).subscribe((context) => { + this.dataSourceProfile = dataSourceProfileService.getProfile({ context }); + }); } /** @@ -79,7 +92,7 @@ export class ProfilesManager { const serializedParams = serializeRootProfileParams(params); if (isEqual(this.prevRootProfileParams, serializedParams)) { - return { getRenderAppWrapper: this.getRootRenderAppWrapper() }; + return { getRenderAppWrapper: this.rootProfile.getRenderAppWrapper }; } const abortController = new AbortController(); @@ -95,13 +108,13 @@ export class ProfilesManager { } if (abortController.signal.aborted) { - return { getRenderAppWrapper: this.getRootRenderAppWrapper() }; + return { getRenderAppWrapper: this.rootProfile.getRenderAppWrapper }; } this.rootContext$.next(context); this.prevRootProfileParams = serializedParams; - return { getRenderAppWrapper: this.getRootRenderAppWrapper() }; + return { getRenderAppWrapper: this.rootProfile.getRenderAppWrapper }; } /** @@ -183,11 +196,13 @@ export class ProfilesManager { */ public getProfiles({ record }: GetProfilesOptions = {}) { return [ - this.rootProfileService.getProfile(this.rootContext$.getValue()), - this.dataSourceProfileService.getProfile(this.dataSourceContext$.getValue()), - this.documentProfileService.getProfile( - recordHasContext(record) ? record.context : this.documentProfileService.defaultContext - ), + this.rootProfile, + this.dataSourceProfile, + this.documentProfileService.getProfile({ + context: recordHasContext(record) + ? record.context + : this.documentProfileService.defaultContext, + }), ]; } @@ -210,11 +225,6 @@ export class ProfilesManager { this.ebtManager.updateProfilesContextWith(dscProfiles); } - - private getRootRenderAppWrapper() { - const rootProfile = this.rootProfileService.getProfile(this.rootContext$.getValue()); - return rootProfile.getRenderAppWrapper; - } } const serializeRootProfileParams = ( diff --git a/test/functional/apps/discover/context_awareness/_data_source_profile.ts b/test/functional/apps/discover/context_awareness/_data_source_profile.ts index 35e3552afa655..eeffafa38cd4e 100644 --- a/test/functional/apps/discover/context_awareness/_data_source_profile.ts +++ b/test/functional/apps/discover/context_awareness/_data_source_profile.ts @@ -20,6 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const dataViews = getService('dataViews'); const dataGrid = getService('dataGrid'); + const retry = getService('retry'); describe('data source profile', () => { describe('ES|QL mode', () => { @@ -98,6 +99,41 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await testSubjects.getVisibleText('docViewerRowDetailsTitle')).to.be('Record #0'); }); }); + + describe('custom context', () => { + it('should render formatted record in doc viewer using formatter from custom context', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await discover.waitUntilSearchingHasFinished(); + await dataGrid.clickRowToggle({ rowIndex: 0, defaultTabId: 'doc_view_example' }); + await retry.try(async () => { + const formattedRecord = await testSubjects.find( + 'exampleDataSourceProfileDocViewRecord' + ); + expect(await formattedRecord.getVisibleText()).to.be( + JSON.stringify( + { + '@timestamp': '2024-06-10T16:00:00.000Z', + 'agent.name': 'java', + 'agent.name.text': 'java', + 'data_stream.type': 'logs', + 'log.level': 'debug', + message: 'This is a debug log', + 'service.name': 'product', + 'service.name.text': 'product', + }, + null, + 2 + ) + ); + }); + }); + }); }); describe('data view mode', () => { @@ -166,6 +202,41 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); }); + + describe('custom context', () => { + it('should render formatted record in doc viewer using formatter from custom context', async () => { + await common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await discover.waitUntilSearchingHasFinished(); + await dataGrid.clickRowToggle({ rowIndex: 0, defaultTabId: 'doc_view_example' }); + await retry.try(async () => { + const formattedRecord = await testSubjects.find( + 'exampleDataSourceProfileDocViewRecord' + ); + expect(await formattedRecord.getVisibleText()).to.be( + JSON.stringify( + { + '@timestamp': ['2024-06-10T16:00:00.000Z'], + 'agent.name': ['java'], + 'agent.name.text': ['java'], + 'data_stream.type': ['logs'], + 'log.level': ['debug'], + message: ['This is a debug log'], + 'service.name': ['product'], + 'service.name.text': ['product'], + _id: 'XdQFDpABfGznVC1bCHLo', + _index: 'my-example-logs', + _score: null, + }, + null, + 2 + ) + ); + }); + }); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts index e03a6d2e081a4..960a79d4bba5d 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts @@ -20,6 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const dataViews = getService('dataViews'); const dataGrid = getService('dataGrid'); + const retry = getService('retry'); describe('data source profile', () => { before(async () => { @@ -98,6 +99,41 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await testSubjects.getVisibleText('docViewerRowDetailsTitle')).to.be('Record #0'); }); }); + + describe('custom context', () => { + it('should render formatted record in doc viewer using formatter from custom context', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await dataGrid.clickRowToggle({ rowIndex: 0, defaultTabId: 'doc_view_example' }); + await retry.try(async () => { + const formattedRecord = await testSubjects.find( + 'exampleDataSourceProfileDocViewRecord' + ); + expect(await formattedRecord.getVisibleText()).to.be( + JSON.stringify( + { + '@timestamp': '2024-06-10T16:00:00.000Z', + 'agent.name': 'java', + 'agent.name.text': 'java', + 'data_stream.type': 'logs', + 'log.level': 'debug', + message: 'This is a debug log', + 'service.name': 'product', + 'service.name.text': 'product', + }, + null, + 2 + ) + ); + }); + }); + }); }); describe('data view mode', () => { @@ -162,6 +198,41 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); }); + + describe('custom context', () => { + it('should render formatted record in doc viewer using formatter from custom context', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await dataGrid.clickRowToggle({ rowIndex: 0, defaultTabId: 'doc_view_example' }); + await retry.try(async () => { + const formattedRecord = await testSubjects.find( + 'exampleDataSourceProfileDocViewRecord' + ); + expect(await formattedRecord.getVisibleText()).to.be( + JSON.stringify( + { + '@timestamp': ['2024-06-10T16:00:00.000Z'], + 'agent.name': ['java'], + 'agent.name.text': ['java'], + 'data_stream.type': ['logs'], + 'log.level': ['debug'], + message: ['This is a debug log'], + 'service.name': ['product'], + 'service.name.text': ['product'], + _id: 'XdQFDpABfGznVC1bCHLo', + _index: 'my-example-logs', + _score: null, + }, + null, + 2 + ) + ); + }); + }); + }); }); }); } From 78f566a4b11411fa0b0c4d1c943b13ba0dff10d5 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 6 Nov 2024 11:52:53 -0600 Subject: [PATCH 32/53] [ci] Validate bundle limits earlier (#199155) Currently pull request builds that create bundle changes over the limits defined in `packages/kbn-optimizer/limits.yml` receive an error in the `Post Build` step. This information is available earlier, after the `Build Distribution` step. By failing earlier we can reduce the number of test runs caused by bundle limit changes. The report created in the `Post Build` step will continue to behave as it currently is. --- .buildkite/scripts/post_build_kibana.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/post_build_kibana.sh b/.buildkite/scripts/post_build_kibana.sh index 2da629e29c158..f6af5b3c20e83 100755 --- a/.buildkite/scripts/post_build_kibana.sh +++ b/.buildkite/scripts/post_build_kibana.sh @@ -9,7 +9,7 @@ if [[ ! "${DISABLE_CI_STATS_SHIPPING:-}" ]]; then "--metrics" "build/kibana/node_modules/@kbn/ui-shared-deps-src/shared_built_assets/metrics.json" ) - if [ "$BUILDKITE_PIPELINE_SLUG" == "kibana-on-merge" ]; then + if [[ "$BUILDKITE_PIPELINE_SLUG" == "kibana-on-merge" ]] || [[ "$BUILDKITE_PIPELINE_SLUG" == "kibana-pull-request" ]]; then cmd+=("--validate") fi From 935c3aa974a22e99a3284a7969217306ba5f3609 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 6 Nov 2024 11:53:23 -0600 Subject: [PATCH 33/53] [kbn-repo-packages] Add sort locale (#199138) In some situations `yarn kbn bootstrap` is causing the sort order of tsconfig.base.json to change. This adds a locale to the comparative to keep the sort order consistent. --- packages/kbn-repo-packages/modern/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-repo-packages/modern/package.js b/packages/kbn-repo-packages/modern/package.js index 3ec33a69e841a..08d16fcf468ec 100644 --- a/packages/kbn-repo-packages/modern/package.js +++ b/packages/kbn-repo-packages/modern/package.js @@ -44,7 +44,7 @@ class Package { * @param {Package} b */ static sorter(a, b) { - return a.manifest.id.localeCompare(b.manifest.id); + return a.manifest.id.localeCompare(b.manifest.id, 'en'); } /** From bc313f4b48680624a0f778eb02eb10158f604a15 Mon Sep 17 00:00:00 2001 From: Samiul Monir <150824886+Samiul-TheSoccerFan@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:04:24 -0500 Subject: [PATCH 34/53] [Search][Index Management] Removing Model deployment from Kibana (#198409) ## Summary Clicking on the `Try Again` button tries to redeploy and receives an error. We tried to disable the `Try Again` button if there are no errors in deployment stage. ### Before https://github.com/user-attachments/assets/b1c7b7ce-afba-42ba-a958-d9ad7cbc8777 ### After https://github.com/user-attachments/assets/15ca3a78-a1fc-4079-8ab6-f4b54e7ed333 --------- Co-authored-by: Elastic Machine --- .../trained_models_deployment_modal.tsx | 40 +------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx index b2e9a1339c3fb..b3dc5244165fb 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx @@ -26,10 +26,8 @@ import React from 'react'; import { EuiLink } from '@elastic/eui'; import { useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { ModelIdMapEntry } from '../../../../components/mappings_editor/components/document_fields/fields'; import { isSemanticTextField } from '../../../../components/mappings_editor/lib/utils'; import { deNormalize } from '../../../../components/mappings_editor/lib'; -import { useMLModelNotificationToasts } from '../../../../../hooks/use_ml_model_status_toasts'; import { useMappingsState } from '../../../../components/mappings_editor/mappings_state_context'; import { useAppContext } from '../../../../app_context'; @@ -55,15 +53,11 @@ export function TrainedModelsDeploymentModal({ }: TrainedModelsDeploymentModalProps) { const modalTitleId = useGeneratedHtmlId(); const { fields, inferenceToModelIdMap } = useMappingsState(); - const { - plugins: { ml }, - url, - } = useAppContext(); + const { url } = useAppContext(); const [isModalVisible, setIsModalVisible] = useState(false); const closeModal = () => setIsModalVisible(false); const [mlManagementPageUrl, setMlManagementPageUrl] = useState(''); const [allowForceSaveMappings, setAllowForceSaveMappings] = useState(false); - const { showErrorToasts, showSuccessfullyDeployedToast } = useMLModelNotificationToasts(); useEffect(() => { const mlLocator = url?.locators.get(ML_APP_LOCATOR); @@ -86,25 +80,6 @@ export function TrainedModelsDeploymentModal({ const [pendingDeployments, setPendingDeployments] = useState([]); - const startModelAllocation = async (entry: ModelIdMapEntry & { inferenceId: string }) => { - try { - await ml?.mlApi?.trainedModels.startModelAllocation(entry.trainedModelId, { - number_of_allocations: 1, - threads_per_allocation: 1, - priority: 'normal', - deployment_id: entry.inferenceId, - }); - showSuccessfullyDeployedToast(entry.trainedModelId); - } catch (error) { - setErrorsInTrainedModelDeployment((previousState) => ({ - ...previousState, - [entry.inferenceId]: error.message, - })); - showErrorToasts(error); - setIsModalVisible(true); - } - }; - useEffect(() => { const models = inferenceIdsInPendingList.map((inferenceId) => inferenceToModelIdMap?.[inferenceId] @@ -114,18 +89,6 @@ export function TrainedModelsDeploymentModal({ } : undefined ); // filter out third-party models - for (const model of models) { - if ( - model?.trainedModelId && - model.isDeployable && - !model.isDownloading && - !model.isDeployed - ) { - // Sometimes the model gets stuck in a ready to deploy state, so we need to trigger deployment manually - // This is currently the only way to surface a specific error message to the user - startModelAllocation(model); - } - } const allPendingDeployments = models .map((model) => { return model?.trainedModelId && !model?.isDeployed ? model?.inferenceId : ''; @@ -135,7 +98,6 @@ export function TrainedModelsDeploymentModal({ (deployment, index) => allPendingDeployments.indexOf(deployment) === index ); setPendingDeployments(uniqueDeployments); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [inferenceIdsInPendingList, inferenceToModelIdMap]); const erroredDeployments = pendingDeployments.filter( From 9f4d88e4b5e491e53aa4cc0b2f6394f5a26e8d9c Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:09:00 +0100 Subject: [PATCH 35/53] Pass bulkPartialUpdate errors to taskStore.errors (#198606) Resolves: #198428 --- .../server/lib/bulk_update_error.ts | 45 +++++++++++++++ .../lib/create_managed_configuration.test.ts | 40 +++++++++++++ .../lib/create_managed_configuration.ts | 6 +- .../task_manager/server/task_store.test.ts | 56 +++++++++++++++++++ .../plugins/task_manager/server/task_store.ts | 17 +++++- 5 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/task_manager/server/lib/bulk_update_error.ts diff --git a/x-pack/plugins/task_manager/server/lib/bulk_update_error.ts b/x-pack/plugins/task_manager/server/lib/bulk_update_error.ts new file mode 100644 index 0000000000000..f7e0552e5a738 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/bulk_update_error.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export class BulkUpdateError extends Error { + private _statusCode: number; + private _type: string; + + constructor({ + statusCode, + message = 'Bulk update failed with unknown reason', + type, + }: { + statusCode: number; + message?: string; + type: string; + }) { + super(message); + this._statusCode = statusCode; + this._type = type; + } + + public get statusCode() { + return this._statusCode; + } + + public get type() { + return this._type; + } +} + +export function getBulkUpdateStatusCode(error: Error | BulkUpdateError): number | undefined { + if (Boolean(error && error instanceof BulkUpdateError)) { + return (error as BulkUpdateError).statusCode; + } +} + +export function getBulkUpdateErrorType(error: Error | BulkUpdateError): string | undefined { + if (Boolean(error && error instanceof BulkUpdateError)) { + return (error as BulkUpdateError).type; + } +} diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts index 1da1bb11d1c5d..d453edc8e7003 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts @@ -15,6 +15,7 @@ import { import { mockLogger } from '../test_utils'; import { CLAIM_STRATEGY_UPDATE_BY_QUERY, CLAIM_STRATEGY_MGET, TaskManagerConfig } from '../config'; import { MsearchError } from './msearch_error'; +import { BulkUpdateError } from './bulk_update_error'; describe('createManagedConfiguration()', () => { let clock: sinon.SinonFakeTimers; @@ -280,6 +281,45 @@ describe('createManagedConfiguration()', () => { expect(subscription).toHaveBeenNthCalledWith(2, 8); }); + test('should decrease configuration at the next interval when a bulkPartialUpdate 429 error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next( + new BulkUpdateError({ statusCode: 429, message: 'test', type: 'too_many_requests' }) + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + + test('should decrease configuration at the next interval when a bulkPartialUpdate 500 error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next( + new BulkUpdateError({ statusCode: 500, message: 'test', type: 'server_error' }) + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + + test('should decrease configuration at the next interval when a bulkPartialUpdate 503 error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next( + new BulkUpdateError({ statusCode: 503, message: 'test', type: 'unavailable' }) + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + test('should not change configuration at the next interval when other msearch error is emitted', async () => { const { subscription, errors$ } = setupScenario(10); errors$.next(new MsearchError(404)); diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts index d13c2511a2a2b..00736f2c36cdb 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts @@ -13,6 +13,7 @@ import { isEsCannotExecuteScriptError } from './identify_es_error'; import { CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY, MAX_CAPACITY, TaskManagerConfig } from '../config'; import { TaskCost } from '../task'; import { getMsearchStatusCode } from './msearch_error'; +import { getBulkUpdateStatusCode } from './bulk_update_error'; const FLUSH_MARKER = Symbol('flush'); export const ADJUST_THROUGHPUT_INTERVAL = 10 * 1000; @@ -169,7 +170,10 @@ function countErrors(errors$: Observable, countInterval: number): Observa isEsCannotExecuteScriptError(e) || getMsearchStatusCode(e) === 429 || getMsearchStatusCode(e) === 500 || - getMsearchStatusCode(e) === 503 + getMsearchStatusCode(e) === 503 || + getBulkUpdateStatusCode(e) === 429 || + getBulkUpdateStatusCode(e) === 500 || + getBulkUpdateStatusCode(e) === 503 ) ) ).pipe( diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index f1374f6d27b76..2238381552861 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -1290,6 +1290,62 @@ describe('TaskStore', () => { ); expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); }); + + test('pushes errors returned by the saved objects client to errors$', async () => { + const task = { + id: '324242', + version: 'WzQsMV0=', + attempts: 3, + }; + + const firstErrorPromise = store.errors$.pipe(first()).toPromise(); + + esClient.bulk.mockResolvedValue({ + errors: true, + items: [ + { + update: { + _id: '1', + _index: 'test-index', + status: 403, + error: { reason: 'Error reason', type: 'cluster_block_exception' }, + }, + }, + ], + took: 10, + }); + + await store.bulkPartialUpdate([task]); + + expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Error reason]`); + }); + + test('pushes errors for the malformed responses to errors$', async () => { + const task = { + id: '324242', + version: 'WzQsMV0=', + attempts: 3, + }; + + const firstErrorPromise = store.errors$.pipe(first()).toPromise(); + + esClient.bulk.mockResolvedValue({ + errors: false, + items: [ + { + update: { + _index: 'test-index', + status: 200, + }, + }, + ], + took: 10, + }); + + await store.bulkPartialUpdate([task]); + + expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: malformed response]`); + }); }); describe('remove', () => { diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 2b3440e87c0f8..b7f1cec3f5567 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -48,6 +48,7 @@ import { claimSort } from './queries/mark_available_tasks_as_claimed'; import { MAX_PARTITIONS } from './lib/task_partitioner'; import { ErrorOutput } from './lib/bulk_operation_buffer'; import { MsearchError } from './lib/msearch_error'; +import { BulkUpdateError } from './lib/bulk_update_error'; export interface StoreOpts { esClient: ElasticsearchClient; @@ -386,11 +387,19 @@ export class TaskStore { } return result.items.map((item) => { + const malformedResponseType = 'malformed response'; + if (!item.update || !item.update._id) { + const err = new BulkUpdateError({ + message: malformedResponseType, + type: malformedResponseType, + statusCode: 500, + }); + this.errors$.next(err); return asErr({ type: 'task', id: 'unknown', - error: { type: 'malformed response' }, + error: { type: malformedResponseType }, }); } @@ -399,6 +408,12 @@ export class TaskStore { : item.update._id; if (item.update?.error) { + const err = new BulkUpdateError({ + message: item.update.error.reason, + type: item.update.error.type, + statusCode: item.update.status, + }); + this.errors$.next(err); return asErr({ type: 'task', id: docId, From 08b83c55f64bc250a4b6d5909cfc2e46003e9751 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Wed, 6 Nov 2024 19:13:33 +0100 Subject: [PATCH 36/53] [Discover] Fix loading data views in ES|QL mode (#199099) - Closes https://github.com/elastic/kibana/issues/199065 ## Summary This PR makes sure that data views are loaded so user can view them after switching from ES|QL mode to data view mode. Looks like it was introduced in https://github.com/elastic/kibana/pull/195670 ### Testing On your local instance having `kibana_sample_data_logs` installed * Open a link to an ES|QL query [link](http://localhost:5601/xot/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),dataSource:(dataViewId:e3465e67bdeced2befff9f9dca7ecf9c48504cad68a10efd881f4c7dd5ade28a,type:dataView),filters:!(),interval:auto,query:(language:kuery,query:''),sort:!(!(timestamp,desc)))) * Switch to classic * Open the DataView Picker, to see a list of DataViews, before this fix there were none ### Checklist - [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 --------- Co-authored-by: Julia Rechkunova Co-authored-by: Julia Rechkunova --- .../application/main/discover_main_route.tsx | 10 ++--- .../apps/discover/esql/_esql_view.ts | 37 +++++++++++++++---- .../page_objects/unified_search_page.ts | 28 ++++++++++++++ .../common/discover/esql/_esql_view.ts | 20 ++++++++++ 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index d86788172386f..205926cf4943b 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -120,14 +120,10 @@ export function DiscoverMainRoute({ const { dataSource } = stateContainer.appState.getState(); const isEsqlQuery = isDataSourceType(dataSource, DataSourceType.Esql); - // ES|QL should work without data views - // Given we have a saved search id, we can skip the data/data view check, too - // A given nextDataView is provided by the user, and therefore we can skip the data/data view check - if (savedSearchId || isEsqlQuery || nextDataView) { - if (!isEsqlQuery) { - await stateContainer.actions.loadDataViewList(); - } + // Although ES|QL doesn't need a data view, we still need to load the data view list to + // ensure the data view is available for the user to switch to classic mode + await stateContainer.actions.loadDataViewList(); return true; } diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index dc550ac5be93d..272de320d2051 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -25,14 +25,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); const esql = getService('esql'); const dashboardAddPanel = getService('dashboardAddPanel'); - const { common, discover, dashboard, header, timePicker, unifiedFieldList } = getPageObjects([ - 'common', - 'discover', - 'dashboard', - 'header', - 'timePicker', - 'unifiedFieldList', - ]); + const dataViews = getService('dataViews'); + const { common, discover, dashboard, header, timePicker, unifiedFieldList, unifiedSearch } = + getPageObjects([ + 'common', + 'discover', + 'dashboard', + 'header', + 'timePicker', + 'unifiedFieldList', + 'unifiedSearch', + ]); const defaultSettings = { defaultIndex: 'logstash-*', @@ -305,6 +308,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); }); + + it('should show available data views after switching to classic mode', async () => { + await discover.selectTextBaseLang(); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + + await browser.refresh(); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + await unifiedSearch.switchToDataViewMode(); + await header.waitUntilLoadingHasFinished(); + await discover.waitUntilSearchingHasFinished(); + const availableDataViews = await unifiedSearch.getDataViewList( + 'discover-dataView-switch-link' + ); + expect(availableDataViews).to.eql(['kibana_sample_data_flights', 'logstash-*']); + await dataViews.switchToAndValidate('kibana_sample_data_flights'); + }); }); describe('inspector', () => { diff --git a/test/functional/page_objects/unified_search_page.ts b/test/functional/page_objects/unified_search_page.ts index 0ccfb388f1f15..4c9c9e5f4976b 100644 --- a/test/functional/page_objects/unified_search_page.ts +++ b/test/functional/page_objects/unified_search_page.ts @@ -27,6 +27,28 @@ export class UnifiedSearchPageObject extends FtrService { ); } + public async getDataViewList(switchButtonSelector: string) { + await this.testSubjects.click(switchButtonSelector); + + await this.retry.waitFor( + 'wait for popover', + async () => await this.testSubjects.exists('indexPattern-switcher') + ); + + const indexPatternSwitcher = await this.testSubjects.find('indexPattern-switcher', 500); + const availableDataViews = await Promise.all( + ( + await indexPatternSwitcher.findAllByCssSelector('.euiSelectableListItem') + ).map(async (item) => { + return await item.getAttribute('title'); + }) + ); + + await this.testSubjects.click(switchButtonSelector); + + return availableDataViews; + } + public async getSelectedDataView(switchButtonSelector: string) { let visibleText = ''; @@ -46,6 +68,12 @@ export class UnifiedSearchPageObject extends FtrService { public async switchToDataViewMode() { await this.testSubjects.click('switch-to-dataviews'); + await this.retry.waitFor('the modal to open', async () => { + return await this.testSubjects.exists('discover-esql-to-dataview-modal'); + }); await this.testSubjects.click('discover-esql-to-dataview-no-save-btn'); + await this.retry.waitFor('the modal to close', async () => { + return !(await this.testSubjects.exists('discover-esql-to-dataview-modal')); + }); } } diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index c82882623b177..0df0676c1f246 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -21,6 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const find = getService('find'); const esql = getService('esql'); const dashboardAddPanel = getService('dashboardAddPanel'); + const dataViews = getService('dataViews'); const PageObjects = getPageObjects([ 'svlCommonPage', 'common', @@ -29,6 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'header', 'timePicker', 'unifiedFieldList', + 'unifiedSearch', ]); const defaultSettings = { @@ -311,6 +313,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); }); + + it('should show available data views after switching to classic mode', async () => { + await PageObjects.discover.selectTextBaseLang(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedSearch.switchToDataViewMode(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const availableDataViews = await PageObjects.unifiedSearch.getDataViewList( + 'discover-dataView-switch-link' + ); + expect(availableDataViews).to.eql(['kibana_sample_data_flights', 'logstash-*']); + await dataViews.switchToAndValidate('kibana_sample_data_flights'); + }); }); describe('inspector', () => { From 83a9acd79b79fa650c610adb13bff1ced2c707fc Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:18:43 -0800 Subject: [PATCH 37/53] Remove deprecated usages of `EuiFormRow`'s `display="columnCompressedSwitch"` prop (#198709) ## Summary This prop option has been deprecated as of https://github.com/elastic/eui/pull/7968. `display="columnCompressed"` now automatically accounts for EuiSwitches (via `:has` CSS) and the extra prop option is no longer needed. ### Checklist - [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 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)) --------- Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> --- .../annotation_editor_controls.tsx | 2 +- .../testing_embedded_lens/public/controls.tsx | 14 +++++++------- .../settings/alerts_settings/alerts_settings.tsx | 2 +- .../knowledge_base/knowledge_base_settings.tsx | 2 +- .../partition_labels/extended_template.tsx | 2 +- .../application/data_drift/data_drift_view.tsx | 2 +- .../components/geo_upload_form/geo_upload_form.tsx | 2 +- .../editor_frame/config_panel/layer_panel.tsx | 3 --- .../axis/extent/axis_extent_settings.tsx | 2 +- .../axis/ticks/axis_ticks_settings.tsx | 2 +- .../ignore_global_filter/settings_control.tsx | 2 +- .../legend/legend_settings_popover.tsx | 4 ++-- .../datatable/components/dimension_editor.tsx | 4 ++-- .../datatable/components/toolbar.tsx | 2 +- .../visualizations/gauge/dimension_editor.tsx | 2 +- .../visualizations/partition/layer_settings.tsx | 2 +- .../tagcloud/tagcloud_toolbar/tagcloud_toolbar.tsx | 2 +- .../xy/xy_config_panel/axis_settings_popover.tsx | 6 +++--- .../missing_values_option.tsx | 2 +- .../vector_style_editor.test.tsx.snap | 4 ++-- .../vector/components/color/dynamic_color_form.tsx | 2 +- .../categorical_data_mapping_popover.tsx | 2 +- .../data_mapping/ordinal_data_mapping_popover.tsx | 2 +- .../vector/components/size/dynamic_size_form.tsx | 2 +- .../vector/components/vector_style_editor.tsx | 2 +- .../public/components/force_refresh_checkbox.tsx | 2 +- .../public/components/global_filter_checkbox.tsx | 2 +- .../public/components/global_time_checkbox.tsx | 2 +- .../layer_settings/layer_settings.tsx | 6 +++--- .../trigger_actions/filter_by_map_extent/modal.tsx | 2 +- .../trigger_actions/synchronize_movement/modal.tsx | 2 +- 31 files changed, 43 insertions(+), 46 deletions(-) diff --git a/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx b/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx index fbef21087cffd..de230ac617987 100644 --- a/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx +++ b/packages/kbn-event-annotation-components/components/annotation_editor_controls/annotation_editor_controls.tsx @@ -386,7 +386,7 @@ const ConfigPanelGenericSwitch = ({ value: boolean; onChange: (event: EuiSwitchEvent) => void; }) => ( - + } helpText={helpText} - display="columnCompressedSwitch" + display="columnCompressed" hasChildLabel={false} > ) : null} {isPieChart(currentAttributes) ? ( - + ) : null} {isHeatmapChart(currentAttributes) ? ( - + ) : null} {isGaugeChart(currentAttributes) ? ( - + } - display="columnCompressedSwitch" + display="columnCompressed" helpText="Pass a consumer defined action to show in the panel context menu." > = React.memo( = ({ onValueChange, argV /> - + - + { onIndexNameValidationEnd={this.props.onIndexNameValidationEnd} /> - + )} - {layerDatasource?.LayerSettingsComponent && visualizationLayerSettings.data ? ( - - ) : null} {activeVisualization?.LayerSettingsComponent && visualizationLayerSettings.data ? ( return ( <> ) /> = ({ label={i18n.translate('xpack.lens.xyChart.missingValuesStyle', { defaultMessage: 'Show as dotted line', })} - display="columnCompressedSwitch" + display="columnCompressed" > {!!styleOptions.useCustomColorRamp ? null : ( - + (props: Props - + <> (props: Props - + <> - + { } return ( - + + + + + + { return !isVectorLayer(props.layer) ? null : ( - + { _renderSwitches() { return mapEmbeddablesSingleton.getMapPanels().map((mapPanel) => { return ( - + { const hasErrors = synchronizedPanels.length === 1 && mapPanel.getIsMovementSynchronized(); return ( Date: Wed, 6 Nov 2024 13:20:49 -0500 Subject: [PATCH 38/53] [Data Usage] setup integration tests (#197112) ## Summary Adds serverless api integration tests and basic functional smoke test for data usage plugin Both are skipped in mki until `xpack.dataUsage.enabled` is enabled by default in serverless ### `GET /internal/api/data_usage/data_streams` ### `POST /internal/api/data_usage/metrics` - skipped in MKI because we'll need to make sure real credentials are being used via the `xpack.dataUsage.autoops*` - we start a mock server at localhost:9000 and set that the config (`xpack.dataUsage.autoops.api.url=http://localhost:9000'`) along with fake credentials for the other `xpack.dataUsage.autoops*` values. If we're not in MKI these values will be used and the mock server will respond to the request at `http://localhost:9000`. If we are in MKI, the real values and credentials should be set, otherwise it will fail as these kibana config values in the tests are not passed into the MKI environment. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../common/rest_types/usage_metrics.ts | 11 +- .../routes/internal/usage_metrics_handler.ts | 17 ++- .../data_usage/server/services/autoops_api.ts | 5 +- .../data_usage/server/services/index.ts | 2 +- .../test_suites/common/data_usage/index.ts | 17 +++ .../test_suites/common/data_usage/mock_api.ts | 23 ++++ .../common/data_usage/mock_data.ts | 43 +++++++ .../common/data_usage/tests/data_streams.ts | 56 +++++++++ .../common/data_usage/tests/metrics.ts | 110 ++++++++++++++++++ .../common_configs/config.group1.ts | 1 + .../test_suites/observability/config.ts | 9 +- .../search/common_configs/config.group1.ts | 1 + .../test_suites/search/config.ts | 8 ++ .../security/common_configs/config.group1.ts | 1 + .../test_suites/security/config.ts | 8 ++ .../page_objects/svl_management_page.ts | 8 ++ .../test_suites/common/data_usage/index.ts | 14 +++ .../test_suites/common/data_usage/main.ts | 35 ++++++ .../common_configs/config.group1.ts | 1 + .../test_suites/observability/config.ts | 12 +- .../search/common_configs/config.group1.ts | 1 + .../functional/test_suites/search/config.ts | 9 +- .../security/common_configs/config.group1.ts | 1 + .../functional/test_suites/security/config.ts | 11 +- x-pack/test_serverless/tsconfig.json | 1 + 25 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts create mode 100644 x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts create mode 100644 x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts create mode 100644 x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts diff --git a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts index 40194494854fc..853da3b3f8cbd 100644 --- a/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts +++ b/x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts @@ -123,6 +123,13 @@ export const UsageMetricsAutoOpsResponseSchema = { ), }), }; -export type UsageMetricsAutoOpsResponseSchemaBody = TypeOf< +export type UsageMetricsAutoOpsResponseMetricSeries = TypeOf< typeof UsageMetricsAutoOpsResponseSchema.body ->; +>['metrics'][MetricTypes][number]; + +export type UsageMetricsAutoOpsResponseSchemaBody = Omit< + TypeOf, + 'metrics' +> & { + metrics: Partial>; +}; diff --git a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts index 93b31033fc4fb..a714259e1e11c 100644 --- a/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts +++ b/x-pack/plugins/data_usage/server/routes/internal/usage_metrics_handler.ts @@ -6,7 +6,6 @@ */ import { RequestHandler } from '@kbn/core/server'; -import { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types'; import { MetricTypes, UsageMetricsAutoOpsResponseSchemaBody, @@ -44,12 +43,22 @@ export const getUsageMetricsHandler = ( new CustomHttpRequestError('[request body.dataStreams]: no data streams selected', 400) ); } + let dataStreamsResponse; - const { data_streams: dataStreamsResponse }: IndicesGetDataStreamResponse = - await esClient.indices.getDataStream({ + try { + // Attempt to fetch data streams + const { data_streams: dataStreams } = await esClient.indices.getDataStream({ name: requestDsNames, expand_wildcards: 'all', }); + dataStreamsResponse = dataStreams; + } catch (error) { + return errorHandler( + logger, + response, + new CustomHttpRequestError('Failed to retrieve data streams', 400) + ); + } const metrics = await dataUsageService.getMetrics({ from, to, @@ -69,7 +78,7 @@ export const getUsageMetricsHandler = ( }; }; -function transformMetricsData( +export function transformMetricsData( data: UsageMetricsAutoOpsResponseSchemaBody ): UsageMetricsResponseSchemaBody { return { diff --git a/x-pack/plugins/data_usage/server/services/autoops_api.ts b/x-pack/plugins/data_usage/server/services/autoops_api.ts index e5ffe24c6167a..6a9de27f996f1 100644 --- a/x-pack/plugins/data_usage/server/services/autoops_api.ts +++ b/x-pack/plugins/data_usage/server/services/autoops_api.ts @@ -13,6 +13,7 @@ import type { AxiosError, AxiosRequestConfig } from 'axios'; import axios from 'axios'; import { LogMeta } from '@kbn/core/server'; import { + UsageMetricsAutoOpsResponseSchema, UsageMetricsAutoOpsResponseSchemaBody, UsageMetricsRequestBody, } from '../../common/rest_types'; @@ -134,8 +135,10 @@ export class AutoOpsAPIService { } ); + const validatedResponse = UsageMetricsAutoOpsResponseSchema.body().validate(response.data); + logger.debug(`[AutoOps API] Successfully created an autoops agent ${response}`); - return response; + return validatedResponse; } private createTlsConfig(autoopsConfig: AutoOpsConfig | undefined) { diff --git a/x-pack/plugins/data_usage/server/services/index.ts b/x-pack/plugins/data_usage/server/services/index.ts index 9ccd08861a26c..3752553e50e9f 100644 --- a/x-pack/plugins/data_usage/server/services/index.ts +++ b/x-pack/plugins/data_usage/server/services/index.ts @@ -41,7 +41,7 @@ export class DataUsageService { metricTypes, dataStreams, }); - return response.data; + return response; } catch (error) { if (error instanceof ValidationError) { throw new AutoOpsError(error.message); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts new file mode 100644 index 0000000000000..cf31e1885d1d5 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Serverless Data Usage APIs', function () { + this.tags(['esGate']); + + loadTestFile(require.resolve('./tests/data_streams')); + loadTestFile(require.resolve('./tests/metrics')); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts new file mode 100644 index 0000000000000..0a9438e826ef3 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_api.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createServer } from '@mswjs/http-middleware'; +import { UsageMetricsAutoOpsResponseSchemaBody } from '@kbn/data-usage-plugin/common/rest_types'; +import { http, HttpResponse, StrictResponse } from 'msw'; +import { mockAutoOpsResponse } from './mock_data'; + +export const setupMockServer = () => { + const server = createServer(autoOpsHandler); + return server; +}; + +const autoOpsHandler = http.post( + '/', + async ({ request }): Promise> => { + return HttpResponse.json(mockAutoOpsResponse); + } +); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts new file mode 100644 index 0000000000000..c38cc57d2b546 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/mock_data.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const mockAutoOpsResponse = { + metrics: { + ingest_rate: [ + { + name: 'metrics-system.cpu-default', + data: [ + [1726858530000, 13756849], + [1726862130000, 14657904], + ], + }, + { + name: 'logs-nginx.access-default', + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + storage_retained: [ + { + name: 'metrics-system.cpu-default', + data: [ + [1726858530000, 12576413], + [1726862130000, 13956423], + ], + }, + { + name: 'logs-nginx.access-default', + data: [ + [1726858530000, 12894623], + [1726862130000, 14436905], + ], + }, + ], + }, +}; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts new file mode 100644 index 0000000000000..d805b8ccff6fe --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/data_streams.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; +import { DataStreamsResponseBodySchemaBody } from '@kbn/data-usage-plugin/common/rest_types'; +import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '@kbn/data-usage-plugin/common'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const svlDatastreamsHelpers = getService('svlDatastreamsHelpers'); + const roleScopedSupertest = getService('roleScopedSupertest'); + let supertestAdminWithCookieCredentials: SupertestWithRoleScope; + const testDataStreamName = 'test-data-stream'; + describe(`GET ${DATA_USAGE_DATA_STREAMS_API_ROUTE}`, function () { + // due to the plugin depending on yml config (xpack.dataUsage.enabled), we cannot test in MKI until it is on by default + this.tags(['skipMKI']); + before(async () => { + await svlDatastreamsHelpers.createDataStream(testDataStreamName); + supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'admin', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); + }); + after(async () => { + await svlDatastreamsHelpers.deleteDataStream(testDataStreamName); + }); + + it('returns created data streams', async () => { + const res = await supertestAdminWithCookieCredentials + .get(DATA_USAGE_DATA_STREAMS_API_ROUTE) + .set('elastic-api-version', '1'); + const dataStreams: DataStreamsResponseBodySchemaBody = res.body; + const foundStream = dataStreams.find((stream) => stream.name === testDataStreamName); + expect(foundStream?.name).to.be(testDataStreamName); + expect(foundStream?.storageSizeBytes).to.be(0); + expect(res.statusCode).to.be(200); + }); + it('returns system indices', async () => { + const res = await supertestAdminWithCookieCredentials + .get(DATA_USAGE_DATA_STREAMS_API_ROUTE) + .set('elastic-api-version', '1'); + const dataStreams: DataStreamsResponseBodySchemaBody = res.body; + const systemDataStreams = dataStreams.filter((stream) => stream.name.startsWith('.')); + expect(systemDataStreams.length).to.be.greaterThan(0); + expect(res.statusCode).to.be(200); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts new file mode 100644 index 0000000000000..8985757ab1cab --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/data_usage/tests/metrics.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import http from 'http'; + +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; +import { UsageMetricsRequestBody } from '@kbn/data-usage-plugin/common/rest_types'; +import { DATA_USAGE_METRICS_API_ROUTE } from '@kbn/data-usage-plugin/common'; +import { transformMetricsData } from '@kbn/data-usage-plugin/server/routes/internal/usage_metrics_handler'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { setupMockServer } from '../mock_api'; +import { mockAutoOpsResponse } from '../mock_data'; + +export default function ({ getService }: FtrProviderContext) { + const svlDatastreamsHelpers = getService('svlDatastreamsHelpers'); + const roleScopedSupertest = getService('roleScopedSupertest'); + let supertestAdminWithCookieCredentials: SupertestWithRoleScope; + const mockAutoopsApiService = setupMockServer(); + describe('Metrics', function () { + let mockApiServer: http.Server; + // due to the plugin depending on yml config (xpack.dataUsage.enabled), we cannot test in MKI until it is on by default + this.tags(['skipMKI']); + + before(async () => { + mockApiServer = mockAutoopsApiService.listen(9000); + supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'admin', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); + }); + + after(() => { + mockApiServer.close(); + }); + describe(`POST ${DATA_USAGE_METRICS_API_ROUTE}`, () => { + const testDataStreamName = 'test-data-stream'; + before(async () => await svlDatastreamsHelpers.createDataStream(testDataStreamName)); + after(async () => await svlDatastreamsHelpers.deleteDataStream(testDataStreamName)); + it('returns 400 with non-existent data streams', async () => { + const requestBody: UsageMetricsRequestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: ['ingest_rate', 'storage_retained'], + dataStreams: ['invalid-data-stream'], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(400); + expect(res.body.message).to.be('Failed to retrieve data streams'); + }); + + it('returns 400 when requesting no data streams', async () => { + const requestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: ['ingest_rate'], + dataStreams: [], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(400); + expect(res.body.message).to.be('[request body.dataStreams]: no data streams selected'); + }); + + it('returns 400 when requesting an invalid metric type', async () => { + const requestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: [testDataStreamName], + dataStreams: ['datastream'], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(400); + expect(res.body.message).to.be( + '[request body.metricTypes]: must be one of ingest_rate, storage_retained, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate' + ); + }); + + it('returns 200 with valid request', async () => { + const requestBody: UsageMetricsRequestBody = { + from: 'now-24h/h', + to: 'now', + metricTypes: ['ingest_rate', 'storage_retained'], + dataStreams: [testDataStreamName], + }; + const res = await supertestAdminWithCookieCredentials + .post(DATA_USAGE_METRICS_API_ROUTE) + .set('elastic-api-version', '1') + .send(requestBody); + expect(res.statusCode).to.be(200); + expect(res.body).to.eql(transformMetricsData(mockAutoOpsResponse)); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts index 4d58d43ea7667..68202b5adf3ad 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Observability API Integration Tests - Common Group 1', diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts index 111686fd95d26..97a30d0f340f9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; import { services as apmServices } from './apm_api_integration/common/services'; import { services as datasetQualityServices } from './dataset_quality_api_integration/common/services'; @@ -26,5 +26,12 @@ export default createTestConfig({ '--xpack.uptime.service.manifestUrl=mockDevUrl', // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts index 4167364e44793..36249651d5167 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts @@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Search API Integration Tests - Common Group 1', diff --git a/x-pack/test_serverless/api_integration/test_suites/search/config.ts b/x-pack/test_serverless/api_integration/test_suites/search/config.ts index 94d7ea67c594a..9f02dc98b88c3 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -21,5 +22,12 @@ export default createTestConfig({ kbnServerArgs: [ // useful for testing (also enabled in MKI QA) '--coreApp.allowDynamicConfigOverrides=true', + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts index 406de533a909d..577b4f8ba4ee0 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Security API Integration Tests - Common Group 1', diff --git a/x-pack/test_serverless/api_integration/test_suites/security/config.ts b/x-pack/test_serverless/api_integration/test_suites/security/config.ts index 0b24438b81591..52b933a22b086 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/config.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -25,5 +26,12 @@ export default createTestConfig({ '--coreApp.allowDynamicConfigOverrides=true', `--xpack.securitySolutionServerless.cloudSecurityUsageReportingTaskInterval=5s`, `--xpack.securitySolutionServerless.usageApi.url=http://localhost:8081`, + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], }); diff --git a/x-pack/test_serverless/functional/page_objects/svl_management_page.ts b/x-pack/test_serverless/functional/page_objects/svl_management_page.ts index e77e77c4fa76c..5676975a89c08 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_management_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_management_page.ts @@ -59,5 +59,13 @@ export function SvlManagementPageProvider({ getService }: FtrProviderContext) { async clickSpacesManagementCard() { await testSubjects.click('app-card-spaces'); }, + + // Data Usage card + async assertDataUsageManagementCardExists() { + await testSubjects.existOrFail('app-card-data_usage'); + }, + async clickDataUsageManagementCard() { + await testSubjects.click('app-card-data_usage'); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts b/x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts new file mode 100644 index 0000000000000..dcdd23b13605f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/data_usage/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext) => { + describe('Data Usage', function () { + loadTestFile(require.resolve('./main')); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts b/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts new file mode 100644 index 0000000000000..fa6e4199bdc09 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/data_usage/main.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['svlCommonPage', 'svlManagementPage', 'common']); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + describe('Main page', function () { + this.tags(['skipMKI']); + before(async () => { + await pageObjects.svlCommonPage.loginAsAdmin(); + await pageObjects.common.navigateToApp('management'); + await retry.waitFor('page to be visible', async () => { + return await testSubjects.exists('cards-navigation-page'); + }); + await pageObjects.svlManagementPage.assertDataUsageManagementCardExists(); + await pageObjects.svlManagementPage.clickDataUsageManagementCard(); + }); + + after(async () => {}); + + it('renders data usage page', async () => { + await retry.waitFor('page to be visible', async () => { + return await testSubjects.exists('DataUsagePage'); + }); + }); + }); +}; diff --git a/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts b/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts index c75e2aa19be75..80f2313cf28a7 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts @@ -22,6 +22,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/painless_lab'), require.resolve('../../common/spaces'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Observability Functional Tests - Common Group 1', diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.ts b/x-pack/test_serverless/functional/test_suites/observability/config.ts index 2610af2a26149..9fffd5623f0a3 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/config.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -18,5 +18,13 @@ export default createTestConfig({ // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml esServerArgs: ['xpack.ml.dfa.enabled=false'], - kbnServerArgs: [], + kbnServerArgs: [ + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, + ], }); diff --git a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts index 3b0991b3b1fe9..11e36186609ec 100644 --- a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts +++ b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts @@ -20,6 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/reporting'), require.resolve('../../common/console'), require.resolve('../../common/spaces'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Search Functional Tests - Common Group 1', diff --git a/x-pack/test_serverless/functional/test_suites/search/config.ts b/x-pack/test_serverless/functional/test_suites/search/config.ts index f330546373525..aef26951908d0 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -22,6 +22,13 @@ export default createTestConfig({ `--xpack.cloud.id=ES3_FTR_TESTS:ZmFrZS1kb21haW4uY2xkLmVsc3RjLmNvJGZha2Vwcm9qZWN0aWQuZXMkZmFrZXByb2plY3RpZC5rYg==`, `--xpack.cloud.serverless.project_name=ES3_FTR_TESTS`, `--xpack.cloud.deployment_url=/projects/elasticsearch/fakeprojectid`, + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, ], apps: { serverlessElasticsearch: { diff --git a/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts b/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts index 8adac3b9c4a6e..fc95c26547c0d 100644 --- a/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts +++ b/x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts @@ -22,6 +22,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/console'), require.resolve('../../common/painless_lab'), require.resolve('../../common/spaces'), + require.resolve('../../common/data_usage'), ], junit: { reportName: 'Serverless Security Functional Tests - Common Group 1', diff --git a/x-pack/test_serverless/functional/test_suites/security/config.ts b/x-pack/test_serverless/functional/test_suites/security/config.ts index 2f6a4b4920a25..1693a07b0e844 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { createTestConfig } from '../../config.base'; export default createTestConfig({ @@ -18,4 +18,13 @@ export default createTestConfig({ // include settings from project controller // https://github.com/elastic/project-controller/blob/main/internal/project/security/config/elasticsearch.yml esServerArgs: ['xpack.ml.nlp.enabled=true'], + kbnServerArgs: [ + '--xpack.dataUsage.enabled=true', + // dataUsage.autoops* config is set in kibana controller + '--xpack.dataUsage.autoops.enabled=true', + '--xpack.dataUsage.autoops.api.url=http://localhost:9000', + `--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`, + ], }); diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index f963f295cd17d..f54ffc1bb4c28 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -92,5 +92,6 @@ "@kbn/core-saved-objects-import-export-server-internal", "@kbn/security-plugin-types-common", "@kbn/ai-assistant-common", + "@kbn/data-usage-plugin", ] } From 1df947949ea17acc75e66b2211d49dd78a5ae0e8 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 6 Nov 2024 11:22:05 -0700 Subject: [PATCH 39/53] [Reporting] Remove "download CSV" export type functionality (#199033) ## Summary Closes https://github.com/elastic/kibana-team/issues/1202 This PR removes the CSV Download feature, and API from Kibana. Any logic that is based on the `xpack.reporting.csv.enablePanelActionDownload` setting has been removed. ## Release Note The functionality that allowed users to download of a CSV export from a dashboard saved search panel without creating a report has been removed. To export CSV data from a dashboard panel, users may use the action menu of a saved search panel in a dashboard to generate a CSV report, and download the report from a toast popup when the report has finished generating. ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) - [x] This will appear in the **Release Notes** and follow the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../kbn-generate-csv/src/generate_csv.test.ts | 2 +- .../src/generate_csv_esql.test.ts | 4 +- .../src/lib/get_export_settings.test.ts | 2 +- packages/kbn-reporting/common/routes.ts | 1 - .../csv/csv_searchsource_immediate.test.ts | 131 ----------- .../csv/csv_searchsource_immediate.ts | 157 ------------- .../kbn-reporting/export_types/csv/index.ts | 1 - .../export_types/csv/tsconfig.json | 2 - .../export_types/csv_common/index.ts | 17 -- .../get_csv_panel_action.test.ts | 55 ----- .../panel_actions/get_csv_panel_action.tsx | 73 +----- .../panel_actions/strings.tsx | 30 +-- .../public/reporting_api_client.test.ts | 26 --- .../public/reporting_api_client.ts | 12 - packages/kbn-reporting/public/types.ts | 1 - .../kbn-reporting/server/config_schema.ts | 2 +- packages/kbn-reporting/server/export_type.ts | 1 - x-pack/plugins/reporting/README.md | 2 - .../__test__/report_listing.test.helpers.tsx | 1 - .../plugins/reporting/server/config/index.ts | 1 + x-pack/plugins/reporting/server/core.ts | 24 +- .../server/lib/event_logger/types.ts | 2 +- .../plugins/reporting/server/routes/index.ts | 7 - .../generate/csv_searchsource_immediate.ts | 137 ------------ .../translations/translations/fr-FR.json | 5 - .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - .../reporting/__snapshots__/download_csv.snap | 35 --- .../group3/reporting/download_csv.ts | 207 ------------------ .../apps/dashboard/group3/reporting/index.ts | 1 - .../test/functional/apps/reporting/README.md | 1 - 31 files changed, 10 insertions(+), 940 deletions(-) delete mode 100644 packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts delete mode 100644 packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts delete mode 100644 x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts delete mode 100644 x-pack/test/functional/apps/dashboard/group3/reporting/__snapshots__/download_csv.snap delete mode 100644 x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts diff --git a/packages/kbn-generate-csv/src/generate_csv.test.ts b/packages/kbn-generate-csv/src/generate_csv.test.ts index faba0a8425e9e..f39cf51352a58 100644 --- a/packages/kbn-generate-csv/src/generate_csv.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv.test.ts @@ -47,7 +47,7 @@ const getMockConfig = (opts: Partial = {}): CsvConfigType => ({ maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: true, + enablePanelActionDownload: false, maxConcurrentShardRequests: 5, ...opts, }); diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts index 4e85431448e45..9ae0b2b711c19 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts @@ -98,7 +98,7 @@ describe('CsvESQLGenerator', () => { maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: true, + enablePanelActionDownload: false, maxConcurrentShardRequests: 5, }; @@ -569,7 +569,7 @@ describe('CsvESQLGenerator', () => { maxSizeBytes: 180000, useByteOrderMarkEncoding: false, scroll: { size: 500, duration: '30s', strategy: 'pit' }, - enablePanelActionDownload: true, + enablePanelActionDownload: false, maxConcurrentShardRequests: 5, }; mockSearchResponse({ diff --git a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts index 9b14636081233..05a321aa1a255 100644 --- a/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts +++ b/packages/kbn-generate-csv/src/lib/get_export_settings.test.ts @@ -39,7 +39,7 @@ describe('getExportSettings', () => { scroll: { size: 500, duration: '30s', strategy: 'pit' }, useByteOrderMarkEncoding: false, maxConcurrentShardRequests: 5, - enablePanelActionDownload: true, + enablePanelActionDownload: false, }; taskInstanceFields = { startedAt: null, retryAt: null }; diff --git a/packages/kbn-reporting/common/routes.ts b/packages/kbn-reporting/common/routes.ts index 77d126b94e08a..dc01746fcc8db 100644 --- a/packages/kbn-reporting/common/routes.ts +++ b/packages/kbn-reporting/common/routes.ts @@ -24,7 +24,6 @@ export const INTERNAL_ROUTES = { DELETE_PREFIX: prefixInternalPath + '/jobs/delete', // docId is added to the final path DOWNLOAD_PREFIX: prefixInternalPath + '/jobs/download', // docId is added to the final path }, - DOWNLOAD_CSV: prefixInternalPath + '/generate/immediate/csv_searchsource', // DEPRECATED GENERATE_PREFIX: prefixInternalPath + '/generate', // exportTypeId is added to the final path }; diff --git a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts b/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts deleted file mode 100644 index 417cc782a54cb..0000000000000 --- a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { CsvGenerator } from '@kbn/generate-csv'; - -jest.mock('@kbn/generate-csv', () => { - return { - CsvGenerator: jest.fn().mockImplementation(() => { - return { generateData: jest.fn() }; - }), - }; -}); - -import { httpServerMock } from '@kbn/core-http-server-mocks'; -import type { CoreStart, KibanaRequest } from '@kbn/core/server'; -import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; -import { discoverPluginMock } from '@kbn/discover-plugin/server/mocks'; -import { createFieldFormatsStartMock } from '@kbn/field-formats-plugin/server/mocks'; -import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; -import { setFieldFormats } from '@kbn/reporting-server'; -import type { Writable } from 'stream'; - -import { CsvSearchSourceImmediateExportType } from '.'; -import { ReportingRequestHandlerContext } from './types'; - -const mockLogger = loggingSystemMock.createLogger(); -const encryptionKey = 'tetkey'; -let stream: jest.Mocked; -let mockCsvSearchSourceImmediateExportType: CsvSearchSourceImmediateExportType; -let mockCoreStart: CoreStart; -let mockRequest: KibanaRequest; -let mockRequestHandlerContext: ReportingRequestHandlerContext; - -beforeEach(async () => { - // use fieldFormats plugin for csv formats - // normally, this is done in the Reporting plugin - setFieldFormats(createFieldFormatsStartMock()); - stream = {} as typeof stream; - - const configType = createMockConfigSchema({ - encryptionKey, - csv: { - checkForFormulas: true, - escapeFormulaValues: true, - maxSizeBytes: 180000, - scroll: { size: 500, duration: 'auto' }, - }, - }); - const mockCoreSetup = coreMock.createSetup(); - mockCoreStart = coreMock.createStart(); - const context = coreMock.createPluginInitializerContext(configType); - mockRequest = httpServerMock.createKibanaRequest(); - - mockCsvSearchSourceImmediateExportType = new CsvSearchSourceImmediateExportType( - mockCoreSetup, - configType, - mockLogger, - context - ); - - mockRequestHandlerContext = { - core: Promise.resolve(mockCoreStart), - } as unknown as ReportingRequestHandlerContext; - - mockCsvSearchSourceImmediateExportType.setup({ - basePath: { set: jest.fn() }, - }); - - mockCsvSearchSourceImmediateExportType.start({ - esClient: elasticsearchServiceMock.createClusterClient(), - savedObjects: mockCoreStart.savedObjects, - uiSettings: mockCoreStart.uiSettings, - discover: discoverPluginMock.createStartContract(), - data: dataPluginMock.createStartContract(), - }); - - jest.useFakeTimers(); - jest.setSystemTime(1630526670000); -}); - -afterEach(() => { - jest.useRealTimers(); -}); - -test('allows csv.scroll.duration to be "auto"', async () => { - const mockGenerateData = jest.fn().mockResolvedValue(() => ({ csv_contains_formulas: false })); - (CsvGenerator as jest.Mock).mockImplementationOnce(() => { - return { generateData: mockGenerateData }; - }); - - await mockCsvSearchSourceImmediateExportType.runTask( - 'cool-job-id', - { browserTimezone: 'US/Alaska', searchSource: {}, title: 'Test Search' }, - mockRequestHandlerContext, - stream, - mockRequest - ); - - expect(CsvGenerator).toBeCalledWith( - { - browserTimezone: 'US/Alaska', - objectType: 'immediate-search', - searchSource: {}, - title: 'Test Search', - }, - { - checkForFormulas: true, - escapeFormulaValues: true, - maxSizeBytes: 180000, - scroll: { duration: 'auto', size: 500 }, - }, - { - retryAt: new Date('2021-09-01T20:06:30.000Z'), - startedAt: new Date('2021-09-01T20:04:30.000Z'), - }, - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything() - ); - - expect(mockGenerateData).toBeCalled(); -}); diff --git a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts b/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts deleted file mode 100644 index 5ca85cb27e540..0000000000000 --- a/packages/kbn-reporting/export_types/csv/csv_searchsource_immediate.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { Writable } from 'stream'; - -import { KibanaRequest } from '@kbn/core-http-server'; -import { DataPluginStart } from '@kbn/data-plugin/server/plugin'; -import { DiscoverServerPluginStart } from '@kbn/discover-plugin/server'; -import { CsvGenerator } from '@kbn/generate-csv'; -import { - CancellationToken, - LICENSE_TYPE_BASIC, - LICENSE_TYPE_CLOUD_STANDARD, - LICENSE_TYPE_ENTERPRISE, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_TRIAL, - durationToNumber, -} from '@kbn/reporting-common'; -import type { TaskRunResult } from '@kbn/reporting-common/types'; -import { - CSV_SEARCHSOURCE_IMMEDIATE_TYPE, - JobParamsDownloadCSV, -} from '@kbn/reporting-export-types-csv-common'; -import type { BaseExportTypeSetupDeps, BaseExportTypeStartDeps } from '@kbn/reporting-server'; -import { ExportType, getFieldFormats } from '@kbn/reporting-server'; - -import { ReportingRequestHandlerContext } from './types'; - -type CsvSearchSourceImmediateExportTypeSetupDeps = BaseExportTypeSetupDeps; -interface CsvSearchSourceImmediateExportTypeStartDeps extends BaseExportTypeStartDeps { - discover: DiscoverServerPluginStart; - data: DataPluginStart; -} - -/* - * ImmediateExecuteFn receives the job doc payload because the payload was - * generated in the ScheduleFn - */ -export type ImmediateExecuteFn = ( - jobId: null, - job: JobParamsDownloadCSV, - context: ReportingRequestHandlerContext, - stream: Writable, - req: KibanaRequest -) => Promise; - -/** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ -export class CsvSearchSourceImmediateExportType extends ExportType< - JobParamsDownloadCSV, - ImmediateExecuteFn, - CsvSearchSourceImmediateExportTypeSetupDeps, - CsvSearchSourceImmediateExportTypeStartDeps -> { - id = CSV_SEARCHSOURCE_IMMEDIATE_TYPE; - name = CSV_SEARCHSOURCE_IMMEDIATE_TYPE; - jobType = CSV_SEARCHSOURCE_IMMEDIATE_TYPE; - jobContentEncoding = 'base64' as const; - jobContentExtension = 'csv' as const; - validLicenses = [ - LICENSE_TYPE_TRIAL, - LICENSE_TYPE_BASIC, - LICENSE_TYPE_CLOUD_STANDARD, - LICENSE_TYPE_GOLD, - LICENSE_TYPE_PLATINUM, - LICENSE_TYPE_ENTERPRISE, - ]; - - constructor(...args: ConstructorParameters) { - super(...args); - this.logger = this.logger.get('csv-searchsource-export'); - } - - public createJob = async () => { - throw new Error(`immediate download has no create job handler!`); - }; - // @ts-ignore expected type failure from deprecated export type - public runTask = async ( - _jobId: string | null, - immediateJobParams: JobParamsDownloadCSV, - context: ReportingRequestHandlerContext, - stream: Writable, - req: KibanaRequest - ) => { - const job = { - objectType: 'immediate-search', - ...immediateJobParams, - }; - - const dataPluginStart = this.startDeps.data; - const savedObjectsClient = (await context.core).savedObjects.client; - const uiSettings = this.getUiSettingsServiceFactory(savedObjectsClient); - const fieldFormatsRegistry = await getFieldFormats().fieldFormatServiceFactory(uiSettings); - - const es = this.startDeps.esClient.asScoped(req); - const searchSourceStart = await dataPluginStart.search.searchSource.asScoped(req); - const clients = { - uiSettings, - data: dataPluginStart.search.asScoped(req), - es, - }; - const dependencies = { - fieldFormatsRegistry, - searchSourceStart, - }; - const cancellationToken = new CancellationToken(); - const csvConfig = this.config.csv; - const taskInstanceFields = - csvConfig.scroll.duration === 'auto' - ? { - startedAt: new Date(), - retryAt: new Date(Date.now() + durationToNumber(this.config.queue.timeout)), - } - : { - startedAt: null, - retryAt: null, - }; - - const csv = new CsvGenerator( - job, - csvConfig, - taskInstanceFields, - clients, - dependencies, - cancellationToken, - this.logger, - stream - ); - const result = await csv.generateData(); - - if (result.csv_contains_formulas) { - this.logger.warn(`CSV may contain formulas whose values have been escaped`); - } - - if (result.max_size_reached) { - this.logger.warn(`Max size reached: CSV output truncated`); - } - - const { warnings } = result; - if (warnings) { - warnings.forEach((warning) => { - this.logger.warn(warning); - }); - } - - return result; - }; -} diff --git a/packages/kbn-reporting/export_types/csv/index.ts b/packages/kbn-reporting/export_types/csv/index.ts index 5910d17926c06..8ad8d200f6b77 100644 --- a/packages/kbn-reporting/export_types/csv/index.ts +++ b/packages/kbn-reporting/export_types/csv/index.ts @@ -9,4 +9,3 @@ export { CsvSearchSourceExportType } from './csv_searchsource'; export { CsvV2ExportType } from './csv_v2'; -export { CsvSearchSourceImmediateExportType } from './csv_searchsource_immediate'; diff --git a/packages/kbn-reporting/export_types/csv/tsconfig.json b/packages/kbn-reporting/export_types/csv/tsconfig.json index 2fef9b63fda79..a5ee79e9b52c4 100644 --- a/packages/kbn-reporting/export_types/csv/tsconfig.json +++ b/packages/kbn-reporting/export_types/csv/tsconfig.json @@ -21,12 +21,10 @@ "@kbn/discover-plugin", "@kbn/data-plugin", "@kbn/generate-csv", - "@kbn/core-http-server", "@kbn/reporting-server", "@kbn/reporting-export-types-csv-common", "@kbn/reporting-mocks-server", "@kbn/core-http-request-handler-context-server", "@kbn/field-formats-plugin", - "@kbn/core-http-server-mocks", ] } diff --git a/packages/kbn-reporting/export_types/csv_common/index.ts b/packages/kbn-reporting/export_types/csv_common/index.ts index 1a2b175347484..259f4a917783a 100644 --- a/packages/kbn-reporting/export_types/csv_common/index.ts +++ b/packages/kbn-reporting/export_types/csv_common/index.ts @@ -18,17 +18,6 @@ import type { export * from './constants'; -/** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ -export interface JobParamsDownloadCSV { - browserTimezone: string; - title: string; - searchSource: SerializedSearchSourceFields; - columns?: string[]; -} - interface BaseParamsCSV { searchSource: SerializedSearchSourceFields; columns?: string[]; @@ -62,12 +51,6 @@ export interface TaskPayloadCsvFromSavedObject extends CsvFromSavedObjectBase, B export const CSV_REPORTING_ACTION = 'generateCsvReport'; -/** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ -export const CSV_SEARCHSOURCE_IMMEDIATE_TYPE = 'csv_searchsource_immediate'; - /** * @deprecated * Supported in case older reports exist in storage diff --git a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts index 72da136ba9223..6eaa3e933980b 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.test.ts @@ -61,7 +61,6 @@ describe('GetCsvReportPanelAction', () => { beforeEach(() => { csvConfig = { scroll: {} as ClientConfigType['csv']['scroll'], - enablePanelActionDownload: false, }; apiClient = new ReportingAPIClient(core.http, core.uiSettings, '7.15.0'); @@ -285,58 +284,4 @@ describe('GetCsvReportPanelAction', () => { expect(await plugin.isCompatible(context)).toEqual(true); }); }); - - describe('download csv', () => { - beforeEach(() => { - csvConfig = { - scroll: {} as ClientConfigType['csv']['scroll'], - enablePanelActionDownload: true, - }; - - core.http.post.mockResolvedValue({}); - }); - - it('shows a success toast when the download successfully starts', async () => { - const panel = new ReportingCsvPanelAction({ - core, - apiClient, - startServices$: mockStartServices$, - usesUiCapabilities: true, - csvConfig, - }); - - await Rx.firstValueFrom(mockStartServices$); - - await panel.execute(context); - - expect(core.notifications.toasts.addSuccess).toHaveBeenCalledWith({ - 'data-test-subj': 'csvDownloadStarted', - text: expect.any(Function), - title: 'CSV download started', - }); - expect(core.notifications.toasts.addDanger).not.toHaveBeenCalled(); - }); - - it('shows a bad old toastie when it unsuccessfully fails', async () => { - apiClient.createImmediateReport = jest.fn().mockRejectedValue('No more ram!'); - const panel = new ReportingCsvPanelAction({ - core, - apiClient, - startServices$: mockStartServices$, - usesUiCapabilities: true, - csvConfig, - }); - - await Rx.firstValueFrom(mockStartServices$); - - await panel.execute(context); - - expect(core.notifications.toasts.addSuccess).toHaveBeenCalled(); - expect(core.notifications.toasts.addDanger).toHaveBeenCalledWith({ - 'data-test-subj': 'downloadCsvFail', - text: "We couldn't download your CSV at this time.", - title: 'CSV download failed', - }); - }); - }); }); diff --git a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx index 7be14579523bd..65712496519f7 100644 --- a/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx +++ b/packages/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx @@ -102,7 +102,6 @@ export class ReportingCsvPanelAction implements ActionDefinition; private readonly notifications: NotificationsSetup; private readonly apiClient: ReportingAPIClient; - private readonly enablePanelActionDownload: boolean; private readonly theme: ThemeServiceSetup; private readonly startServices$: Params['startServices$']; private readonly usesUiCapabilities: boolean; @@ -110,7 +109,6 @@ export class ReportingCsvPanelAction implements ActionDefinition { - const { searchSource, columns, title, analytics, i18nStart } = params; - const immediateJobParams = this.apiClient.getDecoratedJobParams({ - searchSource, - columns, - title, - objectType: 'downloadCsv', // FIXME: added for typescript, but immediate download job does not need objectType - }); - - this.isDownloading = true; - - this.notifications.toasts.addSuccess({ - title: this.i18nStrings.download.toasts.success.title, - text: toMountPoint(this.i18nStrings.download.toasts.success.body, { - analytics, - i18n: i18nStart, - theme: this.theme, - }), - 'data-test-subj': 'csvDownloadStarted', - }); - - await this.apiClient - .createImmediateReport(immediateJobParams) - .then(({ body, response }) => { - this.isDownloading = false; - - const download = `${title}.csv`; - const blob = new Blob([body as BlobPart], { - type: response?.headers.get('content-type') || undefined, - }); - - // Hack for IE11 Support - // @ts-expect-error - if (window.navigator.msSaveOrOpenBlob) { - // @ts-expect-error - return window.navigator.msSaveOrOpenBlob(blob, download); - } - - const a = window.document.createElement('a'); - const downloadObject = window.URL.createObjectURL(blob); - - a.href = downloadObject; - a.download = download; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(downloadObject); - document.body.removeChild(a); - }) - .catch((error: unknown) => { - // eslint-disable-next-line no-console - console.error(error); - this.isDownloading = false; - this.notifications.toasts.addDanger({ - title: this.i18nStrings.download.toasts.error.title, - text: this.i18nStrings.download.toasts.error.body, - 'data-test-subj': 'downloadCsvFail', - }); - }); - }; - private executeGenerate = async (params: ExecutionParams) => { const { searchSource, columns, title, analytics, i18nStart } = params; const csvJobParams = this.apiClient.getDecoratedJobParams({ @@ -278,9 +210,6 @@ export class ReportingCsvPanelAction implements ActionDefinition => ({ - download: { - displayName: i18n.translate('reporting.share.panelAction.downloadCsvPanelTitle', { - defaultMessage: 'Download CSV', - }), - toasts: { - error: { - title: i18n.translate('reporting.share.panelAction.failedCsvReportTitle', { - defaultMessage: `CSV download failed`, - }), - body: i18n.translate('reporting.share.panelAction.failedCsvReportMessage', { - defaultMessage: `We couldn't download your CSV at this time.`, - }), - }, - success: { - title: i18n.translate('reporting.share.panelAction.csvDownloadStartedTitle', { - defaultMessage: `CSV download started`, - }), - body: ( - - ), - }, - }, - }, +export const getI18nStrings = (apiClient: ReportingAPIClient): Record<'generate', I18nStrings> => ({ generate: { displayName: i18n.translate('reporting.share.panelAction.generateCsvPanelTitle', { defaultMessage: 'Generate CSV report', diff --git a/packages/kbn-reporting/public/reporting_api_client.test.ts b/packages/kbn-reporting/public/reporting_api_client.test.ts index c1da4b46ae27a..3504ed87956d4 100644 --- a/packages/kbn-reporting/public/reporting_api_client.test.ts +++ b/packages/kbn-reporting/public/reporting_api_client.test.ts @@ -214,32 +214,6 @@ describe('ReportingAPIClient', () => { }); }); - describe('createImmediateReport', () => { - beforeEach(() => { - httpClient.post.mockResolvedValueOnce({ job: { payload: {} } }); - }); - - it('should send a post request', async () => { - await apiClient.createImmediateReport({ - browserTimezone: 'UTC', - objectType: 'something', - title: 'some title', - version: 'some version', - }); - - expect(httpClient.post).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - body: JSON.stringify({ - browserTimezone: 'UTC', - title: 'some title', - version: 'some version', - }), - }) - ); - }); - }); - describe('getDecoratedJobParams', () => { beforeEach(() => { jest.spyOn(tz, 'guess').mockReturnValue('UTC'); diff --git a/packages/kbn-reporting/public/reporting_api_client.ts b/packages/kbn-reporting/public/reporting_api_client.ts index 48931fee1cf51..66f5daa0c520a 100644 --- a/packages/kbn-reporting/public/reporting_api_client.ts +++ b/packages/kbn-reporting/public/reporting_api_client.ts @@ -215,18 +215,6 @@ export class ReportingAPIClient implements IReportingAPI { } } - /** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ - public async createImmediateReport(baseParams: BaseParams) { - const { objectType: _objectType, ...params } = baseParams; // objectType is not needed for immediate download api - return this.http.post(INTERNAL_ROUTES.DOWNLOAD_CSV, { - asResponse: true, - body: JSON.stringify(params), - }); - } - /** * Adds the browserTimezone and kibana version to report job params */ diff --git a/packages/kbn-reporting/public/types.ts b/packages/kbn-reporting/public/types.ts index 2579ee42fe0dd..e01a1b55b0a1f 100644 --- a/packages/kbn-reporting/public/types.ts +++ b/packages/kbn-reporting/public/types.ts @@ -25,7 +25,6 @@ export interface ReportingPublicPluginStartDependencies { export interface ClientConfigType { csv: { - enablePanelActionDownload: boolean; scroll: { duration: string; size: number; diff --git a/packages/kbn-reporting/server/config_schema.ts b/packages/kbn-reporting/server/config_schema.ts index 7d831f95fc006..a4117341d27ca 100644 --- a/packages/kbn-reporting/server/config_schema.ts +++ b/packages/kbn-reporting/server/config_schema.ts @@ -60,7 +60,7 @@ const CaptureSchema = schema.object({ const CsvSchema = schema.object({ checkForFormulas: schema.boolean({ defaultValue: true }), escapeFormulaValues: schema.boolean({ defaultValue: false }), - enablePanelActionDownload: schema.boolean({ defaultValue: false }), + enablePanelActionDownload: schema.boolean({ defaultValue: false }), // unused as of 9.0 maxSizeBytes: schema.oneOf([schema.number(), schema.byteSize()], { defaultValue: ByteSizeValue.parse('250mb'), }), diff --git a/packages/kbn-reporting/server/export_type.ts b/packages/kbn-reporting/server/export_type.ts index 8ccf69f5073da..2ddd69b826b60 100644 --- a/packages/kbn-reporting/server/export_type.ts +++ b/packages/kbn-reporting/server/export_type.ts @@ -100,7 +100,6 @@ export abstract class ExportType< } } - // needed to be protected vs private for the csv search source immediate export type protected getUiSettingsServiceFactory(savedObjectsClient: SavedObjectsClientContract) { const { uiSettings: uiSettingsService } = this.startDeps; const scopedUiSettingsService = uiSettingsService.asScopedToClient(savedObjectsClient); diff --git a/x-pack/plugins/reporting/README.md b/x-pack/plugins/reporting/README.md index f9d6fe4e3db40..7e4d1beefde19 100644 --- a/x-pack/plugins/reporting/README.md +++ b/x-pack/plugins/reporting/README.md @@ -4,8 +4,6 @@ An awesome Kibana reporting plugin ## csv_searchsource. This is the endpoint used in the Discover UI. It must be replaced by csv_v2 at some point, when we have more capacity in reporting. https://github.com/elastic/kibana/issues/151190 -## csv_searchsource_immediate. -This is deprecated. This export type provides users with the means to download a CSV export of a saved search without creating a report. It is only available to users when `xpack.reporting.csv.enablePanelActionDownload` is set to `true` in kibana.yml. The default is `false`, which provides users with the means to generate a normal CSV report. ## csv_v2. This new endpoint is designed to have a more automation-friendly signature. It will replace csv_searchsource in the UI at some point, when there is more capacity in reporting. It will need a little more work to have parity: it needs to be able to export "unsaved" searches. diff --git a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx index 97c5fae9cc5aa..19337a6f46deb 100644 --- a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx +++ b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx @@ -55,7 +55,6 @@ export const mockConfig: ClientConfigType = { duration: '10m', size: 500, }, - enablePanelActionDownload: false, }, poll: { jobsRefresh: { diff --git a/x-pack/plugins/reporting/server/config/index.ts b/x-pack/plugins/reporting/server/config/index.ts index a44a51d7d9ae4..709d072a9035a 100644 --- a/x-pack/plugins/reporting/server/config/index.ts +++ b/x-pack/plugins/reporting/server/config/index.ts @@ -24,6 +24,7 @@ export const config: PluginConfigDescriptor = { }, schema: ConfigSchema, deprecations: ({ unused }) => [ + unused('csv.enablePanelActionDownload', { level: 'warning' }), // unused since 9.0 unused('queue.indexInterval', { level: 'warning' }), // unused since 8.15 unused('capture.browser.chromium.maxScreenshotDimension', { level: 'warning' }), // unused since 7.8 unused('capture.browser.type', { level: 'warning' }), diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index 794cb5180ea09..283488dd5a973 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -29,11 +29,7 @@ import type { FeaturesPluginSetup } from '@kbn/features-plugin/server'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/server'; import type { ReportingServerInfo } from '@kbn/reporting-common/types'; -import { - CsvSearchSourceExportType, - CsvSearchSourceImmediateExportType, - CsvV2ExportType, -} from '@kbn/reporting-export-types-csv'; +import { CsvSearchSourceExportType, CsvV2ExportType } from '@kbn/reporting-export-types-csv'; import { PdfExportType, PdfV1ExportType } from '@kbn/reporting-export-types-pdf'; import { PngExportType } from '@kbn/reporting-export-types-png'; import type { ReportingConfigType } from '@kbn/reporting-server'; @@ -383,22 +379,4 @@ export class ReportingCore { const ReportingEventLogger = reportingEventLoggerFactory(this.logger); return new ReportingEventLogger(report, task); } - - /** - * @deprecated - * Requires `xpack.reporting.csv.enablePanelActionDownload` set to `true` (default is false) - */ - public async getCsvSearchSourceImmediate() { - const startDeps = await this.getPluginStartDeps(); - - const csvImmediateExport = new CsvSearchSourceImmediateExportType( - this.core, - this.config, - this.logger, - this.context - ); - csvImmediateExport.setup(this.getPluginSetupDeps()); - csvImmediateExport.start({ ...startDeps }); - return csvImmediateExport; - } } diff --git a/x-pack/plugins/reporting/server/lib/event_logger/types.ts b/x-pack/plugins/reporting/server/lib/event_logger/types.ts index 8a6725eb33a4d..4b792446ff4c7 100644 --- a/x-pack/plugins/reporting/server/lib/event_logger/types.ts +++ b/x-pack/plugins/reporting/server/lib/event_logger/types.ts @@ -20,7 +20,7 @@ export interface ReportingAction extends LogMeta { kibana: { reporting: { actionType: A; - id?: string; // "immediate download" exports have no ID + id: string; jobType: string; byteSize?: number; } & TaskRunMetrics; diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index 750cd0d5cd6fb..3473f660f647f 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -9,7 +9,6 @@ import type { Logger } from '@kbn/core/server'; import { ReportingCore } from '..'; import { registerDeprecationsRoutes } from './internal/deprecations/deprecations'; import { registerDiagnosticRoutes } from './internal/diagnostic'; -import { registerGenerateCsvFromSavedObjectImmediate } from './internal/generate/csv_searchsource_immediate'; import { registerGenerationRoutesInternal } from './internal/generate/generate_from_jobparams'; import { registerJobInfoRoutesInternal } from './internal/management/jobs'; import { registerGenerationRoutesPublic } from './public/generate_from_jobparams'; @@ -22,10 +21,4 @@ export function registerRoutes(reporting: ReportingCore, logger: Logger) { registerJobInfoRoutesInternal(reporting); registerGenerationRoutesPublic(reporting, logger); registerJobInfoRoutesPublic(reporting); - - // (deprecated) allow users to download CSV without generating a report - const config = reporting.getConfig(); - if (config.csv.enablePanelActionDownload) { - registerGenerateCsvFromSavedObjectImmediate(reporting, logger); - } } diff --git a/x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts b/x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts deleted file mode 100644 index cc9ad946d84ef..0000000000000 --- a/x-pack/plugins/reporting/server/routes/internal/generate/csv_searchsource_immediate.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Boom from '@hapi/boom'; -import moment from 'moment'; - -import { schema } from '@kbn/config-schema'; -import type { KibanaRequest, Logger } from '@kbn/core/server'; -import { INTERNAL_ROUTES } from '@kbn/reporting-common'; -import { - CSV_SEARCHSOURCE_IMMEDIATE_TYPE, - JobParamsDownloadCSV, -} from '@kbn/reporting-export-types-csv-common'; -import type { ReportingCore } from '../../..'; -import { PassThroughStream } from '../../../lib'; -import { authorizedUserPreRouting, getCounters } from '../../common'; - -const path = INTERNAL_ROUTES.DOWNLOAD_CSV; - -export type CsvFromSavedObjectRequest = KibanaRequest; - -/* - * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: - * - saved object type and ID - * - time range and time zone - * - application state: - * - filters - * - query bar - * - local (transient) changes the user made to the saved object - */ -export function registerGenerateCsvFromSavedObjectImmediate( - reporting: ReportingCore, - parentLogger: Logger -) { - const setupDeps = reporting.getPluginSetupDeps(); - const { router } = setupDeps; - - const useKibanaAccessControl = reporting.getDeprecatedAllowedRoles() === false; // true if deprecated config is turned off - const kibanaAccessControlTags = useKibanaAccessControl ? ['access:downloadCsv'] : []; - - // This API calls run the SearchSourceImmediate export type's runTaskFn directly - router.post( - { - path, - validate: { - body: schema.object({ - columns: schema.maybe(schema.arrayOf(schema.string())), - searchSource: schema.object({}, { unknowns: 'allow' }), - browserTimezone: schema.string({ - defaultValue: 'UTC', - validate: (value) => - moment.tz.zone(value) ? undefined : `Invalid timezone "${typeof value}".`, - }), - title: schema.string(), - version: schema.maybe(schema.string()), - }), - }, - options: { tags: kibanaAccessControlTags, access: 'internal' }, - }, - authorizedUserPreRouting( - reporting, - async (user, context, req: CsvFromSavedObjectRequest, res) => { - const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); - - const logger = parentLogger.get(CSV_SEARCHSOURCE_IMMEDIATE_TYPE); - const csvSearchSourceImmediateExport = await reporting.getCsvSearchSourceImmediate(); - - const stream = new PassThroughStream(); - const eventLog = reporting.getEventLogger({ - jobtype: CSV_SEARCHSOURCE_IMMEDIATE_TYPE, - created_by: user && user.username, - payload: { browserTimezone: req.body.browserTimezone }, - }); - const logError = (error: Error) => { - logger.error(error); - eventLog.logError(error); - }; - - try { - eventLog.logExecutionStart(); - - const taskPromise = csvSearchSourceImmediateExport - .runTask(null, req.body, context, stream, req) - .then((output) => { - logger.info(`Job output size: ${stream.bytesWritten} bytes.`); - - if (!stream.bytesWritten) { - logger.warn('CSV Job Execution created empty content result'); - } - - eventLog.logExecutionComplete({ - ...(output.metrics ?? {}), - byteSize: stream.bytesWritten, - }); - }) - .finally(() => stream.end()); - - await Promise.race([stream.firstBytePromise, taskPromise]); - - taskPromise.catch(logError); - - counters.usageCounter(); - - return res.ok({ - body: stream, - headers: { - 'content-type': 'text/csv;charset=utf-8', - 'accept-ranges': 'none', - }, - }); - } catch (error) { - logError(error); - - if (error instanceof Boom.Boom) { - const statusCode = error.output.statusCode; - counters.errorCounter(undefined, statusCode); - - return res.customError({ - statusCode, - body: error.output.payload.message, - }); - } - - counters.errorCounter(undefined, 500); - - return res.customError({ - statusCode: 500, - }); - } - } - ) - ); -} diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 8dc935b4cc8c9..3ed262c0aa159 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -6074,12 +6074,7 @@ "reporting.share.modalContent.notification.reportingErrorTitle": "Impossible de créer le rapport", "reporting.share.modalContent.successfullyQueuedReportNotificationDescription": "Suivre sa progression dans {path}", "reporting.share.modalContent.successfullyQueuedReportNotificationTitle": "Rapport mis en file d'attente pour {objectType}", - "reporting.share.panelAction.csvDownloadStartedMessage": "Votre CSV sera téléchargé dans un instant.", - "reporting.share.panelAction.csvDownloadStartedTitle": "Téléchargement du CSV démarré", "reporting.share.panelAction.csvReportStartedTitle": "Rapport mis en file d'attente pour CSV", - "reporting.share.panelAction.downloadCsvPanelTitle": "Télécharger CSV", - "reporting.share.panelAction.failedCsvReportMessage": "Nous n'avons pas pu télécharger votre CSV pour le moment.", - "reporting.share.panelAction.failedCsvReportTitle": "Le téléchargement du CSV a échoué", "reporting.share.panelAction.failedGenerateCsvReportMessage": "Nous n'avons pas pu générer votre CSV pour le moment.", "reporting.share.panelAction.failedGenerateCsvReportTitle": "Échec du rapport CSV", "reporting.share.panelAction.generateCsvPanelTitle": "Générer des rapports CSV", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 731d9f556947b..16b4b66d842c4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5828,12 +5828,7 @@ "reporting.share.modalContent.notification.reportingErrorTitle": "レポートを作成できません", "reporting.share.modalContent.successfullyQueuedReportNotificationDescription": "{path}で進捗状況を追跡します。", "reporting.share.modalContent.successfullyQueuedReportNotificationTitle": "{objectType} のレポートキュー", - "reporting.share.panelAction.csvDownloadStartedMessage": "間もなく CSV がダウンロードされます。", - "reporting.share.panelAction.csvDownloadStartedTitle": "CSVダウンロードが開始しました", "reporting.share.panelAction.csvReportStartedTitle": "CSVのキューにあるレポート", - "reporting.share.panelAction.downloadCsvPanelTitle": "CSV をダウンロード", - "reporting.share.panelAction.failedCsvReportMessage": "現在、CSVをダウンロードできませんでした。", - "reporting.share.panelAction.failedCsvReportTitle": "CSV のダウンロードに失敗", "reporting.share.panelAction.failedGenerateCsvReportMessage": "現在 CSV を生成できません。", "reporting.share.panelAction.failedGenerateCsvReportTitle": "CSVレポート失敗", "reporting.share.panelAction.generateCsvPanelTitle": "CSVレポートを生成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2b912cc30c673..2bf1c18e4f4f6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5841,12 +5841,7 @@ "reporting.share.modalContent.notification.reportingErrorTitle": "无法创建报告", "reporting.share.modalContent.successfullyQueuedReportNotificationDescription": "在 {path} 中跟踪其进度。", "reporting.share.modalContent.successfullyQueuedReportNotificationTitle": "已为 {objectType} 排队报告", - "reporting.share.panelAction.csvDownloadStartedMessage": "您的 CSV 将很快下载。", - "reporting.share.panelAction.csvDownloadStartedTitle": "CSV 下载已开始", "reporting.share.panelAction.csvReportStartedTitle": "已为 CSV 排队报告", - "reporting.share.panelAction.downloadCsvPanelTitle": "下载 CSV", - "reporting.share.panelAction.failedCsvReportMessage": "此时无法下载 CSV。", - "reporting.share.panelAction.failedCsvReportTitle": "CSV 下载失败。", "reporting.share.panelAction.failedGenerateCsvReportMessage": "我们此次无法生成 CSV。", "reporting.share.panelAction.failedGenerateCsvReportTitle": "CSV 报告失败", "reporting.share.panelAction.generateCsvPanelTitle": "生成 CSV 报告", diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/__snapshots__/download_csv.snap b/x-pack/test/functional/apps/dashboard/group3/reporting/__snapshots__/download_csv.snap deleted file mode 100644 index c062cae2f66bd..0000000000000 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/__snapshots__/download_csv.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`dashboard Reporting Dashboard Generate CSV Default Saved Search Data Downloads a filtered CSV export of a saved search panel 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jun 22, 2019 @ 00:00:00.000\\",\\"Men's Clothing, Men's Shoes\\",EUR,13,715752,6,\\"Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000\\",\\"ZO0130801308, ZO0402604026, ZO0630506305, ZO0297402974\\" -\\"Jun 22, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Clothing\\",EUR,50,565284,6,\\"Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000\\",\\"ZO0687206872, ZO0422304223\\" -\\"Jun 22, 2019 @ 00:00:00.000\\",\\"Men's Shoes, Men's Accessories\\",EUR,49,565266,6,\\"Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000\\",\\"ZO0255602556, ZO0468304683\\"" -`; - -exports[`dashboard Reporting Dashboard Generate CSV Default Saved Search Data Downloads a saved search panel with a custom time range that does not intersect with dashboard time range 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jun 15, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,732985,6,\\"Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000\\",\\"ZO0048100481, ZO0133401334, ZO0153201532, ZO0381603816\\" -\\"Jun 15, 2019 @ 00:00:00.000\\",\\"Women's Accessories, Women's Clothing, Women's Shoes\\",EUR,5,731613,6,\\"Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000\\",\\"ZO0203502035, ZO0706807068, ZO0072000720, ZO0011200112\\" -\\"Jun 15, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes, Women's Accessories\\",EUR,5,731534,6,\\"Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000, Dec 4, 2016 @ 00:00:00.000\\",\\"ZO0032800328, ZO0026900269, ZO0220602206, ZO0209802098\\"" -`; - -exports[`dashboard Reporting Dashboard Generate CSV Default Saved Search Data Generate CSV export of a saved search panel 1`] = ` -"\\"order_date\\",category,currency,\\"customer_id\\",\\"order_id\\",\\"day_of_week_i\\",\\"products.created_on\\",sku -\\"Jun 22, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",EUR,27,731788,6,\\"Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000\\",\\"ZO0486004860, ZO0177901779, ZO0680506805, ZO0340503405\\" -\\"Jun 22, 2019 @ 00:00:00.000\\",\\"Women's Accessories, Women's Shoes\\",EUR,17,730663,6,\\"Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000\\",\\"ZO0697406974, ZO0370303703, ZO0368103681, ZO0013800138\\" -\\"Jun 22, 2019 @ 00:00:00.000\\",\\"Women's Accessories, Women's Clothing\\",EUR,5,727071,6,\\"Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000, Dec 11, 2016 @ 00:00:00.000\\",\\"ZO0091900919, ZO0660006600, ZO0197001970, ZO0074600746\\"" -`; - -exports[`dashboard Reporting Dashboard Generate CSV Field Formatters and Scripted Fields Generate CSV export of a saved search panel 1`] = ` -"date,\\"_id\\",name,gender,value,year,\\"years_ago\\",\\"date_informal\\" -\\"Jan 1, 1982 @ 00:00:00.000\\",\\"1982-Fethany-F\\",Fethany,F,780,1982,\\"37.00000000000000000000\\",\\"Jan 1st 82\\" -" -`; - -exports[`dashboard Reporting Dashboard Generate CSV Filtered Saved Search Downloads filtered Discover saved search report 1`] = ` -"\\"order_date\\",category,\\"order_id\\",\\"customer_full_name\\",\\"taxful_total_price\\",currency -\\"Jun 25, 2019 @ 00:00:00.000\\",\\"Women's Clothing\\",569144,\\"Betty Perkins\\",\\"61.969\\",EUR -\\"Jun 25, 2019 @ 00:00:00.000\\",\\"Women's Clothing, Women's Shoes\\",568503,\\"Betty Bryant\\",68,EUR -\\"Jun 25, 2019 @ 00:00:00.000\\",\\"Women's Accessories\\",568229,\\"Betty Reese\\",\\"22.984\\",EUR" -`; diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts deleted file mode 100644 index ea2a66028ada5..0000000000000 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/download_csv.ts +++ /dev/null @@ -1,207 +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 { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; -import { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const reportingService = getService('reporting'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const filterBar = getService('filterBar'); - const retry = getService('retry'); - const toasts = getService('toasts'); - const { reporting, common, dashboard, timePicker } = getPageObjects([ - 'reporting', - 'common', - 'dashboard', - 'timePicker', - ]); - - const navigateToDashboardApp = async () => { - log.debug('in navigateToDashboardApp'); - await dashboard.navigateToApp(); - await retry.tryForTime(10000, async () => { - expect(await dashboard.onDashboardLandingPage()).to.be(true); - }); - }; - - const getCsvReportData = async () => { - await toasts.dismissAll(); - const url = await reporting.getReportURL(60000); - const res = await reporting.getResponse(url ?? ''); - - expect(res.status).to.equal(200); - expect(res.get('content-type')).to.equal('text/csv; charset=utf-8'); - return res.text; - }; - - const clickDownloadCsv = async (wrapper?: WebElementWrapper) => { - log.debug('click "Generate CSV"'); - await dashboardPanelActions.clickPanelAction( - 'embeddablePanelAction-generateCsvReport', - wrapper - ); - await testSubjects.existOrFail('csvReportStarted'); // validate toast panel - }; - - const clickDownloadCsvByTitle = async (title?: string) => { - log.debug(`click "Generate CSV" on "${title}"`); - await dashboardPanelActions.clickPanelActionByTitle( - 'embeddablePanelAction-generateCsvReport', - title - ); - await testSubjects.existOrFail('csvReportStarted'); // validate toast panel - }; - - const createPartialCsv = (csvFile: unknown) => { - const partialCsvFile = (csvFile as string).split('\n').slice(0, 4); - return partialCsvFile.join('\n'); - }; - - /* - * Tests - */ - describe('Dashboard Generate CSV', () => { - describe('Default Saved Search Data', () => { - before(async () => { - await esArchiver.emptyKibanaIndex(); - await reportingService.initEcommerce(); - await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); - }); - - beforeEach(async () => { - await navigateToDashboardApp(); - }); - - after(async () => { - await reportingService.teardownEcommerce(); - }); - - it('Generate CSV export of a saved search panel', async function () { - await dashboard.loadSavedDashboard('Ecom Dashboard - 3 Day Period'); - await clickDownloadCsvByTitle('EcommerceData'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(76137); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - - it('Downloads a filtered CSV export of a saved search panel', async function () { - await dashboard.loadSavedDashboard('Ecom Dashboard - 3 Day Period'); - - // add a filter - await filterBar.addFilter({ field: 'category', operation: 'is', value: `Men's Shoes` }); - await clickDownloadCsvByTitle('EcommerceData'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(17106); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - - it('Downloads a saved search panel with a custom time range that does not intersect with dashboard time range', async function () { - await dashboard.loadSavedDashboard('Ecom Dashboard - 3 Day Period - custom time range'); - await clickDownloadCsvByTitle('EcommerceData'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(23277); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - - it('Gets the correct filename if panel titles are hidden', async () => { - await dashboard.loadSavedDashboard('Ecom Dashboard Hidden Panel Titles'); - const savedSearchPanel = await dashboardPanelActions.getPanelWrapperById( - '94eab06f-60ac-4a85-b771-3a8ed475c9bb' - ); // panel title is hidden - - await clickDownloadCsv(savedSearchPanel); - await testSubjects.existOrFail('csvReportStarted'); - - const csvFile = await getCsvReportData(); - expect(csvFile).to.not.be(null); - }); - }); - - describe('Filtered Saved Search', () => { - const TEST_SEARCH_TITLE = 'Customer Betty'; - const TEST_DASHBOARD_TITLE = 'Filtered Search Data'; - const from = 'Jun 20, 2019 @ 23:56:51.374'; - const to = 'Jun 25, 2019 @ 16:18:51.821'; - - before(async () => { - await esArchiver.emptyKibanaIndex(); - await reportingService.initEcommerce(); - await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); - }); - - beforeEach(async () => { - await navigateToDashboardApp(); - log.info(`Creating empty dashboard`); - await dashboard.clickNewDashboard(); - await timePicker.setAbsoluteRange(from, to); - log.info(`Adding "${TEST_SEARCH_TITLE}" to dashboard`); - await dashboardAddPanel.addSavedSearch(TEST_SEARCH_TITLE); - await dashboard.saveDashboard(TEST_DASHBOARD_TITLE); - }); - - after(async () => { - await reportingService.teardownEcommerce(); - await common.unsetTime(); - }); - - it('Downloads filtered Discover saved search report', async () => { - await clickDownloadCsvByTitle(TEST_SEARCH_TITLE); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(2446); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - }); - - describe('Field Formatters and Scripted Fields', () => { - const dashboardWithScriptedFieldsSearch = 'names dashboard'; - - before(async () => { - await esArchiver.emptyKibanaIndex(); - await reportingService.initLogs(); - await esArchiver.load('x-pack/test/functional/es_archives/reporting/hugedata'); - await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'UTC' }); - }); - - beforeEach(async () => { - await navigateToDashboardApp(); - await dashboard.loadSavedDashboard(dashboardWithScriptedFieldsSearch); - await timePicker.setAbsoluteRange( - 'Nov 26, 1981 @ 21:54:15.526', - 'Mar 5, 1982 @ 18:17:44.821' - ); - - await common.sleep(1000); - await filterBar.addFilter({ field: 'name.keyword', operation: 'is', value: 'Fethany' }); - await common.sleep(1000); - }); - - after(async () => { - await reportingService.teardownLogs(); - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/hugedata'); - }); - - it('Generate CSV export of a saved search panel', async () => { - await clickDownloadCsvByTitle('namessearch'); - - const csvFile = await getCsvReportData(); - expect(csvFile.length).to.be(166); - expectSnapshot(createPartialCsv(csvFile)).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts b/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts index ef9821a544b3a..9dfbd61473302 100644 --- a/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts +++ b/x-pack/test/functional/apps/dashboard/group3/reporting/index.ts @@ -10,6 +10,5 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Reporting', function () { loadTestFile(require.resolve('./screenshots')); - loadTestFile(require.resolve('./download_csv')); }); } diff --git a/x-pack/test/functional/apps/reporting/README.md b/x-pack/test/functional/apps/reporting/README.md index 5d47804fef284..ed2402e9d9d58 100644 --- a/x-pack/test/functional/apps/reporting/README.md +++ b/x-pack/test/functional/apps/reporting/README.md @@ -9,7 +9,6 @@ Functional tests on report generation are under the applications that use report - `x-pack/test/functional/apps/visualize/reporting.ts` **CSV Report testing:** - - `x-pack/test/functional/apps/dashboard/reporting/download_csv.ts` - `x-pack/test/functional/apps/discover/reporting.ts` Reporting Management app tests are in `functional/apps/reporting_management`. From 437ca8bd31d896c666d15b0ccd03d1fb2f0aee9d Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 6 Nov 2024 11:22:32 -0700 Subject: [PATCH 40/53] [unified-data-table] clean up another SASS compilation warnings (#199176) ## Summary Follows https://github.com/elastic/kibana/pull/198876. I found one more instance of SASS code that created compilation warnings. --- packages/kbn-unified-data-table/src/components/data_table.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index f530e870665f8..a9d8f26a3c68a 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -99,7 +99,7 @@ align-items: start; .euiDataGridHeaderCell__draggableIcon { - padding-block: $euiSizeXS / 2; // to align with a token height + padding-block: calc($euiSizeXS / 2); // to align with a token height } .euiDataGridHeaderCell__button { From fb7114222b5bde5846fdda85a2ae0e94797600e2 Mon Sep 17 00:00:00 2001 From: Bryce Buchanan <75274611+bryce-b@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:02:41 -0800 Subject: [PATCH 41/53] added '\' escapes (#199185) ## Summary This PR adds backslash escaping to previous code scan fixes. --- .../metrics_explorer/components/helpers/create_tsvb_link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts index 3192ccbf6f980..e41d191549c5a 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts @@ -114,7 +114,7 @@ export const createFilterFromOptions = ( filters.push(options.filterQuery); } if (options.groupBy) { - const id = series.id.replace(/"/g, '\\"'); + const id = series.id.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); const groupByFilters = Array.isArray(options.groupBy) ? options.groupBy .map((field, index) => { From f740d953c7c624ddc26e1bef7a88dd9bb901a451 Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:51:25 -0500 Subject: [PATCH 42/53] [Security Solution] Adds UI support for filtering by rule source customization (#197340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Addresses https://github.com/elastic/kibana/issues/180169 > [!NOTE] > Feature is behind the `prebuiltRulesCustomizationEnabled` feature flag. Adds a filter for prebuilt rules in the Update rules table for "Modified" and "Unmodified" rules. Also adds a badge column in the Rules table to display whether a prebuilt rule has been customized or not. Also switches the "Customized Elastic rule" badge on the rule details page to align with the updated language of "_Modified_ Elastic rule" ### Screenshots #### Modified badge in Rules table ![Screenshot 2024-11-05 at 3 05 56 PM](https://github.com/user-attachments/assets/1f3313bb-7171-42b5-99b0-b9fb296fefd3) #### Modification filter dropdown on Rule update page Screenshot 2024-10-24 at 11 46 26 AM #### New "customized rule" badge language on Rule details page ![Screenshot 2024-11-05 at 3 14 58 PM](https://github.com/user-attachments/assets/4e22ba3a-e13f-4cf1-88c0-6b5b0b2c258a) ### 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] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels) - [ ] This will appear in the **Release Notes** and follow the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Elastic Machine --- .../customized_prebuilt_rule_badge.tsx | 8 +- .../components/rule_details/translations.ts | 4 +- .../hooks/use_default_index_pattern.tsx | 6 +- ...s_prebuilt_rules_customization_enabled.tsx | 12 ++ .../rule_management/logic/types.ts | 6 + .../upgrade_prebuilt_rules_table_context.tsx | 7 +- .../upgrade_prebuilt_rules_table_filters.tsx | 48 ++++++-- ...rade_rule_customization_filter_popover.tsx | 92 +++++++++++++++ .../use_filter_prebuilt_rules_to_upgrade.ts | 28 ++++- .../use_prebuilt_rules_upgrade_state.ts | 6 +- ...e_upgrade_prebuilt_rules_table_columns.tsx | 44 ++++++- .../components/rules_table/use_columns.tsx | 54 ++++++++- .../integrations_popover/index.tsx | 7 +- .../detection_engine/rules/translations.ts | 35 ++++++ ...low_with_prebuilt_rule_customization.cy.ts | 110 ++++++++++++++++++ .../cypress/screens/alerts_detection_rules.ts | 2 + .../cypress/screens/rule_updates.ts | 12 ++ .../cypress/tasks/api_calls/rules.ts | 17 +++ .../cypress/tasks/prebuilt_rules.ts | 10 ++ 19 files changed, 470 insertions(+), 38 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/update_workflow_with_prebuilt_rule_customization.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx index 56a559a91794a..e4b00196f4768 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/customized_prebuilt_rule_badge.tsx @@ -10,7 +10,7 @@ import { EuiBadge } from '@elastic/eui'; import * as i18n from './translations'; import { isCustomizedPrebuiltRule } from '../../../../../common/api/detection_engine'; import type { RuleResponse } from '../../../../../common/api/detection_engine'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../hooks/use_is_prebuilt_rules_customization_enabled'; interface CustomizedPrebuiltRuleBadgeProps { rule: RuleResponse | null; @@ -19,9 +19,7 @@ interface CustomizedPrebuiltRuleBadgeProps { export const CustomizedPrebuiltRuleBadge: React.FC = ({ rule, }) => { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); if (!isPrebuiltRulesCustomizationEnabled) { return null; @@ -31,5 +29,5 @@ export const CustomizedPrebuiltRuleBadge: React.FC{i18n.CUSTOMIZED_PREBUILT_RULE_LABEL}; + return {i18n.MODIFIED_PREBUILT_RULE_LABEL}; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 89c22a285e327..e7f36e2011f3c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -350,10 +350,10 @@ export const MAX_SIGNALS_FIELD_LABEL = i18n.translate( } ); -export const CUSTOMIZED_PREBUILT_RULE_LABEL = i18n.translate( +export const MODIFIED_PREBUILT_RULE_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.customizedPrebuiltRuleLabel', { - defaultMessage: 'Customized Elastic rule', + defaultMessage: 'Modified Elastic rule', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx index b5ca86c6f1f57..5450cf9950d59 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_default_index_pattern.tsx @@ -7,7 +7,7 @@ import { useKibana } from '../../../common/lib/kibana/kibana_react'; import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../common/constants'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from './use_is_prebuilt_rules_customization_enabled'; /** * Gets the default index pattern for cases when rule has neither index patterns or data view. @@ -15,9 +15,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_exper */ export function useDefaultIndexPattern(): string[] { const { services } = useKibana(); - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); return isPrebuiltRulesCustomizationEnabled ? services.settings.client.get(DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx new file mode 100644 index 0000000000000..d25925860c175 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; + +export const useIsPrebuiltRulesCustomizationEnabled = () => { + return useIsExperimentalFeatureEnabled('prebuiltRulesCustomizationEnabled'); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index e12442c97aa4c..59ac52d592bcd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -99,6 +99,7 @@ export interface FilterOptions { excludeRuleTypes?: Type[]; enabled?: boolean; // undefined is to display all the rules ruleExecutionStatus?: RuleExecutionStatus; // undefined means "all" + ruleSource?: RuleCustomizationEnum[]; // undefined is to display all the rules } export interface FetchRulesResponse { @@ -202,3 +203,8 @@ export interface FindRulesReferencedByExceptionsProps { lists: FindRulesReferencedByExceptionsListProp[]; signal?: AbortSignal; } + +export enum RuleCustomizationEnum { + customized = 'CUSTOMIZED', + not_customized = 'NOT_CUSTOMIZED', +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index aad1e053e15b6..6ec9ffdd02e67 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -8,8 +8,8 @@ import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RulesUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { RuleUpgradeConflictsResolverTab } from '../../../../rule_management/components/rule_details/three_way_diff/rule_upgrade_conflicts_resolver_tab'; import { PerFieldRuleDiffTab } from '../../../../rule_management/components/rule_details/per_field_rule_diff_tab'; import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages'; @@ -111,13 +111,12 @@ interface UpgradePrebuiltRulesTableContextProviderProps { export const UpgradePrebuiltRulesTableContextProvider = ({ children, }: UpgradePrebuiltRulesTableContextProviderProps) => { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const [loadingRules, setLoadingRules] = useState([]); const [filterOptions, setFilterOptions] = useState({ filter: '', tags: [], + ruleSource: [], }); const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx index 215a810bf3aa2..900d81d0b0037 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_filters.tsx @@ -9,10 +9,13 @@ import { EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; +import type { RuleCustomizationEnum } from '../../../../rule_management/logic'; import * as i18n from './translations'; import { TagsFilterPopover } from '../rules_table_filters/tags_filter_popover'; import { RuleSearchField } from '../rules_table_filters/rule_search_field'; import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context'; +import { RuleCustomizationFilterPopover } from './upgrade_rule_customization_filter_popover'; const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeM}; @@ -28,7 +31,9 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { actions: { setFilterOptions }, } = useUpgradePrebuiltRulesTableContext(); - const { tags: selectedTags } = filterOptions; + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); + + const { tags: selectedTags, ruleSource: selectedRuleSource = [] } = filterOptions; const handleOnSearch = useCallback( (filterString: string) => { @@ -52,22 +57,45 @@ const UpgradePrebuiltRulesTableFiltersComponent = () => { [selectedTags, setFilterOptions] ); + const handleSelectedRuleSource = useCallback( + (newRuleSource: RuleCustomizationEnum[]) => { + if (!isEqual(newRuleSource, selectedRuleSource)) { + setFilterOptions((filters) => ({ + ...filters, + ruleSource: newRuleSource, + })); + } + }, + [selectedRuleSource, setFilterOptions] + ); + return ( - + - - - + + {isPrebuiltRulesCustomizationEnabled && ( + + + + )} + + + + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx new file mode 100644 index 0000000000000..234943e333272 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_rule_customization_filter_popover.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo } from 'react'; +import type { EuiSelectableOption } from '@elastic/eui'; +import { EuiFilterButton, EuiPopover, EuiSelectable } from '@elastic/eui'; +import { RuleCustomizationEnum } from '../../../../rule_management/logic'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; +import { toggleSelectedGroup } from '../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; + +interface RuleCustomizationFilterPopoverProps { + selectedRuleSource: RuleCustomizationEnum[]; + onSelectedRuleSourceChanged: (newRuleSource: RuleCustomizationEnum[]) => void; +} + +const RULE_CUSTOMIZATION_POPOVER_WIDTH = 200; + +const RuleCustomizationFilterPopoverComponent = ({ + selectedRuleSource, + onSelectedRuleSourceChanged, +}: RuleCustomizationFilterPopoverProps) => { + const [isRuleCustomizationPopoverOpen, setIsRuleCustomizationPopoverOpen] = useState(false); + + const selectableOptions: EuiSelectableOption[] = useMemo( + () => [ + { + label: i18n.MODIFIED_LABEL, + key: RuleCustomizationEnum.customized, + checked: selectedRuleSource.includes(RuleCustomizationEnum.customized) ? 'on' : undefined, + }, + { + label: i18n.UNMODIFIED_LABEL, + key: RuleCustomizationEnum.not_customized, + checked: selectedRuleSource.includes(RuleCustomizationEnum.not_customized) + ? 'on' + : undefined, + }, + ], + [selectedRuleSource] + ); + + const handleSelectableOptionsChange = ( + newOptions: EuiSelectableOption[], + _: unknown, + changedOption: EuiSelectableOption + ) => { + toggleSelectedGroup( + changedOption.key ?? '', + selectedRuleSource, + onSelectedRuleSourceChanged as (args: string[]) => void + ); + }; + + const triggerButton = ( + setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} + numFilters={selectableOptions.length} + isSelected={isRuleCustomizationPopoverOpen} + hasActiveFilters={selectedRuleSource.length > 0} + numActiveFilters={selectedRuleSource.length} + data-test-subj="rule-customization-filter-popover-button" + > + {i18n.RULE_SOURCE} + + ); + + return ( + setIsRuleCustomizationPopoverOpen(!isRuleCustomizationPopoverOpen)} + panelPaddingSize="none" + repositionOnScroll + panelProps={{ + 'data-test-subj': 'rule-customization-filter-popover', + }} + > + + {(list) =>
{list}
} +
+
+ ); +}; + +export const RuleCustomizationFilterPopover = React.memo(RuleCustomizationFilterPopoverComponent); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts index 342a1e6e8768e..b5a0e123d7510 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_filter_prebuilt_rules_to_upgrade.ts @@ -7,9 +7,12 @@ import { useMemo } from 'react'; import type { RuleUpgradeInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules'; -import type { FilterOptions } from '../../../../rule_management/logic/types'; +import { RuleCustomizationEnum, type FilterOptions } from '../../../../rule_management/logic/types'; -export type UpgradePrebuiltRulesTableFilterOptions = Pick; +export type UpgradePrebuiltRulesTableFilterOptions = Pick< + FilterOptions, + 'filter' | 'tags' | 'ruleSource' +>; export const useFilterPrebuiltRulesToUpgrade = ({ rules, @@ -19,7 +22,7 @@ export const useFilterPrebuiltRulesToUpgrade = ({ filterOptions: UpgradePrebuiltRulesTableFilterOptions; }) => { const filteredRules = useMemo(() => { - const { filter, tags } = filterOptions; + const { filter, tags, ruleSource } = filterOptions; return rules.filter((ruleInfo) => { if (filter && !ruleInfo.current_rule.name.toLowerCase().includes(filter.toLowerCase())) { return false; @@ -29,6 +32,25 @@ export const useFilterPrebuiltRulesToUpgrade = ({ return tags.every((tag) => ruleInfo.current_rule.tags.includes(tag)); } + if (ruleSource && ruleSource.length > 0) { + if ( + ruleSource.includes(RuleCustomizationEnum.customized) && + ruleSource.includes(RuleCustomizationEnum.not_customized) + ) { + return true; + } else if ( + ruleSource.includes(RuleCustomizationEnum.customized) && + ruleInfo.current_rule.rule_source.type === 'external' + ) { + return ruleInfo.current_rule.rule_source.is_customized; + } else if ( + ruleSource.includes(RuleCustomizationEnum.not_customized) && + ruleInfo.current_rule.rule_source.type === 'external' + ) { + return ruleInfo.current_rule.rule_source.is_customized === false; + } + } + return true; }); }, [filterOptions, rules]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index 29c5b2b201fe6..8c97a4ef52e2b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -6,7 +6,7 @@ */ import { useCallback, useMemo, useState } from 'react'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RulesUpgradeState, FieldsUpgradeState, @@ -33,9 +33,7 @@ interface UseRulesUpgradeStateResult { export function usePrebuiltRulesUpgradeState( ruleUpgradeInfos: RuleUpgradeInfoForReview[] ): UseRulesUpgradeStateResult { - const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled( - 'prebuiltRulesCustomizationEnabled' - ); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const [rulesResolvedConflicts, setRulesResolvedConflicts] = useState({}); const setRuleFieldResolvedValue = useCallback( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index 09009c98c2858..579f571f80e79 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -6,7 +6,14 @@ */ import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiBadge, EuiButtonEmpty, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { + EuiBadge, + EuiButtonEmpty, + EuiLink, + EuiLoadingSpinner, + EuiText, + EuiToolTip, +} from '@elastic/eui'; import React, { useMemo } from 'react'; import type { RuleUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade/rule_upgrade_state'; import { RulesTableEmptyColumnName } from '../rules_table_empty_column_name'; @@ -104,6 +111,35 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; +const MODIFIED_COLUMN: TableColumn = { + field: 'current_rule.rule_source', + name: , + align: 'center', + render: (ruleSource: Rule['rule_source']) => { + if ( + ruleSource == null || + ruleSource.type === 'internal' || + (ruleSource.type === 'external' && ruleSource.is_customized === false) + ) { + return null; + } + + return ( + + + {i18n.MODIFIED_LABEL} + + + ); + }, + width: '90px', + truncateText: true, +}; + const createUpgradeButtonColumn = ( upgradeRules: UpgradePrebuiltRulesTableActions['upgradeRules'], loadingRules: RuleSignatureId[], @@ -154,9 +190,15 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => { } = useUpgradePrebuiltRulesTableContext(); const isDisabled = isRefetching || isUpgradingSecurityPackages; + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ RULE_NAME_COLUMN, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index c38fec638e478..ae24b2bb482d3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -46,6 +46,7 @@ import { useRulesTableActions } from './use_rules_table_actions'; import { MlRuleWarningPopover } from '../ml_rule_warning_popover/ml_rule_warning_popover'; import { getMachineLearningJobId } from '../../../../detections/pages/detection_engine/rules/helpers'; import type { TimeRange } from '../../../rule_gaps/types'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; export type TableColumn = EuiBasicTableColumn | EuiTableActionsColumnType; @@ -233,6 +234,35 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; +const MODIFIED_COLUMN: TableColumn = { + field: 'rule_source', + name: , + align: 'center', + render: (ruleSource: Rule['rule_source']) => { + if ( + ruleSource == null || + ruleSource.type === 'internal' || + (ruleSource.type === 'external' && ruleSource.is_customized === false) + ) { + return null; + } + + return ( + + + {i18n.MODIFIED_LABEL} + + + ); + }, + width: '90px', + truncateText: true, +}; + const useActionsColumn = ({ showExceptionsDuplicateConfirmation, showManualRuleRunConfirmation, @@ -265,6 +295,7 @@ export const useRulesColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -279,9 +310,15 @@ export const useRulesColumns = ({ }); const snoozeColumn = useRuleSnoozeColumn(); + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ ruleNameColumn, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { @@ -352,13 +389,14 @@ export const useRulesColumns = ({ ...(hasCRUDPermissions ? [actionsColumn] : []), ], [ - actionsColumn, - enabledColumn, + ruleNameColumn, + isPrebuiltRulesCustomizationEnabled, + showRelatedIntegrations, executionStatusColumn, snoozeColumn, + enabledColumn, hasCRUDPermissions, - ruleNameColumn, - showRelatedIntegrations, + actionsColumn, ] ); }; @@ -380,6 +418,7 @@ export const useMonitoringColumns = ({ }); const ruleNameColumn = useRuleNameColumn(); const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledColumn = useEnabledColumn({ hasCRUDPermissions, isLoadingJobs, @@ -393,12 +432,18 @@ export const useMonitoringColumns = ({ mlJobs, }); + // TODO: move this change to the `INTEGRATIONS_COLUMN` when `prebuiltRulesCustomizationEnabled` feature flag is removed + if (isPrebuiltRulesCustomizationEnabled) { + INTEGRATIONS_COLUMN.width = '70px'; + } + return useMemo( () => [ { ...ruleNameColumn, width: '28%', }, + ...(isPrebuiltRulesCustomizationEnabled ? [MODIFIED_COLUMN] : []), ...(showRelatedIntegrations ? [INTEGRATIONS_COLUMN] : []), TAGS_COLUMN, { @@ -503,6 +548,7 @@ export const useMonitoringColumns = ({ enabledColumn, executionStatusColumn, hasCRUDPermissions, + isPrebuiltRulesCustomizationEnabled, ruleNameColumn, showRelatedIntegrations, ] diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx index 2d2875d5a8734..49b6c1d1e4e99 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx @@ -16,6 +16,7 @@ import { EuiSpacer, } from '@elastic/eui'; +import { useIsPrebuiltRulesCustomizationEnabled } from '../../../../../detection_engine/rule_management/hooks/use_is_prebuilt_rules_customization_enabled'; import type { RelatedIntegrationArray } from '../../../../../../common/api/detection_engine/model/rule_schema'; import { IntegrationDescription } from '../integrations_description'; import { useRelatedIntegrations } from '../use_related_integrations'; @@ -54,6 +55,7 @@ const IntegrationListItem = styled('li')` const IntegrationsPopoverComponent = ({ relatedIntegrations }: IntegrationsPopoverProps) => { const [isPopoverOpen, setPopoverOpen] = useState(false); const { integrations, isLoaded } = useRelatedIntegrations(relatedIntegrations); + const isPrebuiltRulesCustomizationEnabled = useIsPrebuiltRulesCustomizationEnabled(); const enabledIntegrations = useMemo(() => { return integrations.filter( @@ -65,10 +67,13 @@ const IntegrationsPopoverComponent = ({ relatedIntegrations }: IntegrationsPopov const numIntegrationsEnabled = enabledIntegrations.length; const badgeTitle = useMemo(() => { + if (isPrebuiltRulesCustomizationEnabled) { + return isLoaded ? `${numIntegrationsEnabled}/${numIntegrations}` : `${numIntegrations}`; + } return isLoaded ? `${numIntegrationsEnabled}/${numIntegrations} ${i18n.INTEGRATIONS_BADGE}` : `${numIntegrations} ${i18n.INTEGRATIONS_BADGE}`; - }, [isLoaded, numIntegrations, numIntegrationsEnabled]); + }, [isLoaded, isPrebuiltRulesCustomizationEnabled, numIntegrations, numIntegrationsEnabled]); return ( { + describe('Upgrade of prebuilt rules with conflicts', () => { + const RULE_1_ID = 'rule_1'; + const RULE_2_ID = 'rule_2'; + const OUTDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Outdated rule 1', + rule_id: RULE_1_ID, + version: 1, + }); + const UPDATED_RULE_1 = createRuleAssetSavedObject({ + name: 'Updated rule 1', + rule_id: RULE_1_ID, + version: 2, + }); + const OUTDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Outdated rule 2', + rule_id: RULE_2_ID, + version: 1, + }); + const UPDATED_RULE_2 = createRuleAssetSavedObject({ + name: 'Updated rule 2', + rule_id: RULE_2_ID, + version: 2, + }); + const patchedName = 'A new name that creates a conflict'; + beforeEach(() => { + login(); + resetRulesTableState(); + deleteAlertsAndRules(); + cy.intercept('POST', '/internal/detection_engine/prebuilt_rules/upgrade/_perform').as( + 'updatePrebuiltRules' + ); + /* Create a new rule and install it */ + createAndInstallMockedPrebuiltRules([OUTDATED_RULE_1, OUTDATED_RULE_2]); + /* Modify one of the rule's name to cause a conflict */ + patchRule(OUTDATED_RULE_1['security-rule'].rule_id, { + name: patchedName, + }); + /* Create a second version of the rule, making it available for update */ + installPrebuiltRuleAssets([UPDATED_RULE_1, UPDATED_RULE_2]); + + visitRulesManagementTable(); + clickRuleUpdatesTab(); + }); + + it('should filter by customized prebuilt rules', () => { + // Filter table to show modified rules only + filterPrebuiltRulesUpdateTableByRuleCustomization('Modified'); + cy.get(MODIFIED_RULE_BADGE).should('exist'); + + // Verify only rules with customized rule sources are displayed + cy.get(RULES_UPDATES_TABLE).contains(patchedName); + assertRulesNotPresentInRuleUpdatesTable([OUTDATED_RULE_2]); + }); + + it('should filter by customized prebuilt rules', () => { + // Filter table to show unmodified rules only + filterPrebuiltRulesUpdateTableByRuleCustomization('Unmodified'); + cy.get(MODIFIED_RULE_BADGE).should('not.exist'); + + // Verify only rules with non-customized rule sources are displayed + assertRulesPresentInRuleUpdatesTable([OUTDATED_RULE_2]); + cy.get(patchedName).should('not.exist'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts index bbc7a346b252f..e2ce41dee2847 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts_detection_rules.ts @@ -349,3 +349,5 @@ export const ESQL_QUERY_VALUE = '[data-test-subj="esqlQueryPropertyValue"]'; export const PER_FIELD_DIFF_WRAPPER = '[data-test-subj="ruleUpgradePerFieldDiffWrapper"]'; export const PER_FIELD_DIFF_DEFINITION_SECTION = '[data-test-subj="perFieldDiffDefinitionSection"]'; + +export const MODIFIED_RULE_BADGE = '[data-test-subj="upgradeRulesTableModifiedColumnBadge"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts b/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts new file mode 100644 index 0000000000000..4b11a4624c3e2 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/rule_updates.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON = + '[data-test-subj="rule-customization-filter-popover-button"]'; + +export const RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL = + '[data-test-subj="rule-customization-filter-popover"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts index 008fc92bd0224..c0ef625f52e35 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts @@ -39,6 +39,23 @@ export const createRule = ( ); }; +export const patchRule = ( + ruleId: string, + updateData: Partial +): Cypress.Chainable> => { + return cy.currentSpace().then((spaceId) => + rootRequest({ + method: 'PATCH', + url: spaceId ? getSpaceUrl(spaceId, DETECTION_ENGINE_RULES_URL) : DETECTION_ENGINE_RULES_URL, + body: { + rule_id: ruleId, + ...updateData, + }, + failOnStatusCode: false, + }) + ); +}; + /** * Snoozes a rule via API * diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts index b4f68dba976c3..d4148d5e632a1 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/prebuilt_rules.ts @@ -17,6 +17,10 @@ import { TOASTER, } from '../screens/alerts_detection_rules'; import type { SAMPLE_PREBUILT_RULE } from './api_calls/prebuilt_rules'; +import { + RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON, + RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL, +} from '../screens/rule_updates'; export const clickAddElasticRulesButton = () => { cy.get(ADD_ELASTIC_RULES_BTN).click(); @@ -150,3 +154,9 @@ export const assertRulesNotPresentInRuleUpdatesTable = ( cy.get(rule['security-rule'].name).should('not.exist'); } }; + +export const filterPrebuiltRulesUpdateTableByRuleCustomization = (text: string) => { + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON).click(); + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_PANEL).contains(text).click(); + cy.get(RULE_UPGRADE_TABLE_MODIFICATION_FILTER_BUTTON).click(); +}; From 6b7c5b9f046ace37cf5dc980eb07ea267b17e1e8 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 6 Nov 2024 13:59:47 -0600 Subject: [PATCH 43/53] [ci] Remove plugin build (#197125) The platform plugin builds were used when functional tests were, at times, run from source. This is mostly no longer a requirement. There are two remaining cypress scripts that I updated to use the build instead. With the time saved I'm dropping the number of vCPUs from 16 to 8. These are mostly underutilized by this step, with the exception of the distribution plugin build. --- .buildkite/pipelines/chrome_forward_testing.yml | 4 ++-- .../es_serverless/verify_es_serverless_image.yml | 4 ++-- .buildkite/pipelines/es_snapshots/verify.yml | 4 ++-- .buildkite/pipelines/fips.yml | 4 ++-- .buildkite/pipelines/flaky_tests/pipeline.ts | 2 +- .buildkite/pipelines/on_merge.yml | 4 ++-- .buildkite/pipelines/on_merge_unsupported_ftrs.yml | 4 ++-- .buildkite/pipelines/performance/daily.yml | 4 ++-- .../performance/data_set_extraction_daily.yml | 4 ++-- .buildkite/pipelines/pull_request/base.yml | 4 ++-- .../scalability/api_capacity_testing_daily.yml | 4 ++-- .buildkite/scripts/build_kibana_plugins.sh | 7 ------- .buildkite/scripts/download_build_artifacts.sh | 5 +---- .buildkite/scripts/post_build_kibana_plugins.sh | 13 ------------- .buildkite/scripts/steps/build_kibana.sh | 2 -- .../steps/functional/defend_workflows_burn.sh | 5 +---- .../functional/defend_workflows_serverless_burn.sh | 5 +---- .../scripts/steps/on_merge_build_and_metrics.sh | 2 -- .../synthetics/.buildkite/pipelines/flaky.js | 4 ++-- .../uptime/.buildkite/pipelines/flaky.js | 4 ++-- .../ux/.buildkite/pipelines/flaky.js | 4 ++-- 21 files changed, 30 insertions(+), 63 deletions(-) delete mode 100755 .buildkite/scripts/build_kibana_plugins.sh delete mode 100755 .buildkite/scripts/post_build_kibana_plugins.sh diff --git a/.buildkite/pipelines/chrome_forward_testing.yml b/.buildkite/pipelines/chrome_forward_testing.yml index eb80625a73d77..965f910e8e07e 100644 --- a/.buildkite/pipelines/chrome_forward_testing.yml +++ b/.buildkite/pipelines/chrome_forward_testing.yml @@ -20,9 +20,9 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml b/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml index fa8162ca93c0e..3058293bae793 100644 --- a/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml +++ b/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml @@ -31,13 +31,13 @@ steps: provider: gcp machineType: n2-standard-2 - - label: "Build Kibana Distribution and Plugins" + - label: "Build Kibana Distribution" command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" depends_on: pre-build diff --git a/.buildkite/pipelines/es_snapshots/verify.yml b/.buildkite/pipelines/es_snapshots/verify.yml index d91af527c8266..e8c9899e9aed7 100755 --- a/.buildkite/pipelines/es_snapshots/verify.yml +++ b/.buildkite/pipelines/es_snapshots/verify.yml @@ -23,12 +23,12 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/fips.yml b/.buildkite/pipelines/fips.yml index 09ae10496456f..f4a5b3623bbcc 100644 --- a/.buildkite/pipelines/fips.yml +++ b/.buildkite/pipelines/fips.yml @@ -17,9 +17,9 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/flaky_tests/pipeline.ts b/.buildkite/pipelines/flaky_tests/pipeline.ts index ec1b4994beae3..0d6c3b5c40f7d 100644 --- a/.buildkite/pipelines/flaky_tests/pipeline.ts +++ b/.buildkite/pipelines/flaky_tests/pipeline.ts @@ -122,7 +122,7 @@ const pipeline = { steps.push({ command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', + label: 'Build Kibana Distribution', agents: expandAgentQueue('c2-8'), key: 'build', if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''", diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 65c7f0095f063..e5d28465b2dc3 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -17,12 +17,12 @@ steps: - wait - command: .buildkite/scripts/steps/on_merge_build_and_metrics.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build timeout_in_minutes: 60 diff --git a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml index cf1a523be5a32..a7800fcc92dce 100644 --- a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml +++ b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml @@ -13,12 +13,12 @@ steps: limit: 1 - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/performance/daily.yml b/.buildkite/pipelines/performance/daily.yml index bbbdde955df76..1eeb044304d22 100644 --- a/.buildkite/pipelines/performance/daily.yml +++ b/.buildkite/pipelines/performance/daily.yml @@ -9,13 +9,13 @@ steps: - wait - - label: '🧑‍🏭 Build Kibana Distribution and Plugins' + - label: '🧑‍🏭 Build Kibana Distribution' command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: c2-standard-16 + machineType: n2-standard-8 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/performance/data_set_extraction_daily.yml b/.buildkite/pipelines/performance/data_set_extraction_daily.yml index bb2c33f7a2d33..dcb7355318960 100644 --- a/.buildkite/pipelines/performance/data_set_extraction_daily.yml +++ b/.buildkite/pipelines/performance/data_set_extraction_daily.yml @@ -10,13 +10,13 @@ steps: - wait - - label: ':building_construction: Build Kibana Distribution and Plugins' + - label: ':building_construction: Build Kibana Distribution ' command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: c2-standard-16 + machineType: n2-standard-8 key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index e7b593f464b54..f56159882b2af 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -8,9 +8,9 @@ steps: - wait - command: .buildkite/scripts/steps/build_kibana.sh - label: Build Kibana Distribution and Plugins + label: Build Kibana Distribution agents: - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml b/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml index 785726bf95174..909329234c328 100644 --- a/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml +++ b/.buildkite/pipelines/scalability/api_capacity_testing_daily.yml @@ -9,13 +9,13 @@ steps: - wait - - label: 'Build Kibana Distribution and Plugins' + - label: 'Build Kibana Distribution' command: .buildkite/scripts/steps/build_kibana.sh agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod provider: gcp - machineType: n2-standard-16 + machineType: n2-standard-8 preemptible: true key: build if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" diff --git a/.buildkite/scripts/build_kibana_plugins.sh b/.buildkite/scripts/build_kibana_plugins.sh deleted file mode 100755 index 4e566008b3351..0000000000000 --- a/.buildkite/scripts/build_kibana_plugins.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -echo "--- Build Platform Plugins" -THREADS=$(grep -c ^processor /proc/cpuinfo) -node scripts/build_kibana_platform_plugins --examples --test-plugins --workers "$THREADS" --no-inspect-workers --no-progress diff --git a/.buildkite/scripts/download_build_artifacts.sh b/.buildkite/scripts/download_build_artifacts.sh index 1b52e82c2d66b..497efa181018e 100755 --- a/.buildkite/scripts/download_build_artifacts.sh +++ b/.buildkite/scripts/download_build_artifacts.sh @@ -6,12 +6,11 @@ source "$(dirname "$0")/common/util.sh" if [[ "${KIBANA_BUILD_ID:-}" != "false" ]]; then if [[ ! -d "$KIBANA_BUILD_LOCATION/bin" ]]; then - echo '--- Downloading Distribution and Plugin artifacts' + echo '--- Downloading Distribution' cd "$WORKSPACE" download_artifact kibana-default.tar.gz . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" - download_artifact kibana-default-plugins.tar.gz . --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" mkdir -p "$KIBANA_BUILD_LOCATION" tar -xzf kibana-default.tar.gz -C "$KIBANA_BUILD_LOCATION" --strip=1 @@ -24,7 +23,5 @@ if [[ "${KIBANA_BUILD_ID:-}" != "false" ]]; then fi cd "$KIBANA_DIR" - - tar -xzf ../kibana-default-plugins.tar.gz fi fi diff --git a/.buildkite/scripts/post_build_kibana_plugins.sh b/.buildkite/scripts/post_build_kibana_plugins.sh deleted file mode 100755 index 288fd262cafa9..0000000000000 --- a/.buildkite/scripts/post_build_kibana_plugins.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -echo "--- Archive built plugins" -shopt -s globstar -tar -zcf \ - target/kibana-default-plugins.tar.gz \ - x-pack/plugins/**/target/public \ - x-pack/test/**/target/public \ - examples/**/target/public \ - x-pack/examples/**/target/public \ - test/**/target/public diff --git a/.buildkite/scripts/steps/build_kibana.sh b/.buildkite/scripts/steps/build_kibana.sh index bf4a9ff243d02..4bad332dd073f 100755 --- a/.buildkite/scripts/steps/build_kibana.sh +++ b/.buildkite/scripts/steps/build_kibana.sh @@ -4,6 +4,4 @@ set -euo pipefail .buildkite/scripts/bootstrap.sh .buildkite/scripts/build_kibana.sh -.buildkite/scripts/build_kibana_plugins.sh -.buildkite/scripts/post_build_kibana_plugins.sh .buildkite/scripts/post_build_kibana.sh diff --git a/.buildkite/scripts/steps/functional/defend_workflows_burn.sh b/.buildkite/scripts/steps/functional/defend_workflows_burn.sh index 6a97ba4e82b33..44d87ba4a6635 100644 --- a/.buildkite/scripts/steps/functional/defend_workflows_burn.sh +++ b/.buildkite/scripts/steps/functional/defend_workflows_burn.sh @@ -4,10 +4,7 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh -.buildkite/scripts/bootstrap.sh -.buildkite/scripts/copy_es_snapshot_cache.sh -node scripts/build_kibana_platform_plugins.js - +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} export JOB=kibana-defend-workflows-cypress buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" 'false' diff --git a/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh b/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh index 4bebee15953e6..a092ffbd4186b 100644 --- a/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh +++ b/.buildkite/scripts/steps/functional/defend_workflows_serverless_burn.sh @@ -4,10 +4,7 @@ set -euo pipefail source .buildkite/scripts/steps/functional/common.sh -.buildkite/scripts/bootstrap.sh -.buildkite/scripts/copy_es_snapshot_cache.sh -node scripts/build_kibana_platform_plugins.js - +export KIBANA_INSTALL_DIR=${KIBANA_BUILD_LOCATION} export JOB=kibana-defend-workflows-serverless-cypress buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" 'false' diff --git a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh index fb05bb99b0c54..1f1e492f87bec 100755 --- a/.buildkite/scripts/steps/on_merge_build_and_metrics.sh +++ b/.buildkite/scripts/steps/on_merge_build_and_metrics.sh @@ -4,7 +4,5 @@ set -euo pipefail .buildkite/scripts/bootstrap.sh .buildkite/scripts/build_kibana.sh -.buildkite/scripts/build_kibana_plugins.sh -.buildkite/scripts/post_build_kibana_plugins.sh .buildkite/scripts/post_build_kibana.sh .buildkite/scripts/saved_object_field_metrics.sh diff --git a/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js b/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js index 7a1ee5c582d7d..ca024e9b42286 100644 --- a/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/observability_solution/synthetics/.buildkite/pipelines/flaky.js @@ -63,8 +63,8 @@ console.log(JSON.stringify(pipeline, null, 2)); function getBuildJob() { return { command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', - agents: { queue: 'c2-8' }, + label: 'Build Kibana Distribution', + agents: { queue: 'n2-8' }, key: BUILD_UUID, if: `build.env('${KIBANA_BUILD_ID}') == null || build.env('${KIBANA_BUILD_ID}') == ''`, }; diff --git a/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js b/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js index 7a1ee5c582d7d..ca024e9b42286 100644 --- a/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/observability_solution/uptime/.buildkite/pipelines/flaky.js @@ -63,8 +63,8 @@ console.log(JSON.stringify(pipeline, null, 2)); function getBuildJob() { return { command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', - agents: { queue: 'c2-8' }, + label: 'Build Kibana Distribution', + agents: { queue: 'n2-8' }, key: BUILD_UUID, if: `build.env('${KIBANA_BUILD_ID}') == null || build.env('${KIBANA_BUILD_ID}') == ''`, }; diff --git a/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js b/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js index 6a01e8c52d002..b9a1782820aeb 100644 --- a/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js +++ b/x-pack/plugins/observability_solution/ux/.buildkite/pipelines/flaky.js @@ -64,8 +64,8 @@ console.log(JSON.stringify(pipeline, null, 2)); function getBuildJob() { return { command: '.buildkite/scripts/steps/build_kibana.sh', - label: 'Build Kibana Distribution and Plugins', - agents: { queue: 'c2-8' }, + label: 'Build Kibana Distribution', + agents: { queue: 'n2-8' }, key: BUILD_UUID, if: `build.env('${KIBANA_BUILD_ID}') == null || build.env('${KIBANA_BUILD_ID}') == ''`, }; From 3a5fa5f4463346b56379e3cf80247db19b7e7cbb Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 6 Nov 2024 20:01:16 +0000 Subject: [PATCH 44/53] skip flaky suite (#195672) --- .../user_actions/delete_attachment_confirmation_modal.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx index aa19cc2a473a4..0d9d90058eaf9 100644 --- a/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx @@ -11,7 +11,8 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import { DeleteAttachmentConfirmationModal } from './delete_attachment_confirmation_modal'; -describe('DeleteAttachmentConfirmationModal', () => { +// FLAKY: https://github.com/elastic/kibana/issues/195672 +describe.skip('DeleteAttachmentConfirmationModal', () => { let appMock: AppMockRenderer; const props = { title: 'My title', From bab2c2a497efcb93cb3a46e085b2df50e4839010 Mon Sep 17 00:00:00 2001 From: wajihaparvez Date: Wed, 6 Nov 2024 15:01:34 -0500 Subject: [PATCH 45/53] [Docs] 8.15.4 release notes (#198565) ## Summary Adding a section for 8.15.4 release notes. --------- Co-authored-by: Florent Le Borgne Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- docs/CHANGELOG.asciidoc | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 22d5aaa2877c3..f030de4645e3f 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -11,6 +11,7 @@ Review important information about the {kib} 8.x releases. * <> +* <> * <> * <> * <> @@ -430,6 +431,33 @@ Management:: * Fixes the pagination of the source documents data grid in Transforms ({kibana-pull}196119[#196119]). * Fixes autocomplete suggestions after a comma in Console ({kibana-pull}189656[#189656]). +[[release-notes-8.15.4]] +== {kib} 8.15.4 + +The 8.15.4 release includes the following bug fixes. + +[float] +[[fixes-v8.15.4]] +=== Bug fixes +Dashboards and visualizations:: +* Fixes incomplete string escaping and encoding in *TSVB* ({kibana-pull}196248[#196248]). +* Adds scroll margin to panels ({kibana-pull}193430[#193430]). +* Fixes an issue where label truncation in heat map legends was not working properly in *Lens* ({kibana-pull}195928[#195928]). +Discover:: +* Fixes the width for saved object Type column ({kibana-pull}194388[#194388]). +Elastic Observability solution:: +* Changes the slice outcome from bad to good whenever there is no data during the slice window ({kibana-pull}196942[#196942]). +Elastic Search solution:: +* Fixes a bug with the {ref}/es-connectors-network-drive.html[Network Drive connector] where advanced configuration fields were not displayed for CSV file role mappings with `Drive Type: Linux` selected ({kibana-pull}195567[#195567]). +Elastic Security solution:: +For the Elastic Security 8.15.4 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Kibana platform:: +* Fixes an issue causing the wrong date to show in the header of a report when generated from a relative date ({kibana-pull}197027[#197027]). +* Fixes an issue with the export options for PNG/PDF reports in a dashboard ({kibana-pull}192530[#192530]). +Machine Learning:: +* Fixes an issue preventing Anomaly swim lane panels from updating on query changes ({kibana-pull}195090[#195090]). +Management:: +* Fixes the pagination of the source documents data grid in Transforms ({kibana-pull}196119[#196119]). [[release-notes-8.15.3]] == {kib} 8.15.3 @@ -456,7 +484,6 @@ Elastic Security solution:: For the Elastic Security 8.15.3 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. Kibana security:: * Automatic Import no longer asks the LLM to map fields to reserved ECS fields ({kibana-pull}195168[#195168]). -* Automatic Import no longer returns an "Invalid ECS field" message when the ECS mapping slightly differs from the expected format. For example `date_format` instead of `date_formats` ({kibana-pull}195167[#195167]). * Fixes an issue that was causing the Grok processor to return non-ECS compatible fields when processing structured or unstructured syslog samples in Automatic Import ({kibana-pull}194727[#194727]). * Fixes the integrationName when uploading a new version of an existing integration using a ZIP upload ({kibana-pull}194298[#194298]). * Fixes a bug that caused the Deploy step of Automatic Import to fail after a pipeline was edited and saved ({kibana-pull}194203[#194203]). From 8e62f4e83fc53b014152d467e77505b321caf917 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 6 Nov 2024 20:04:06 +0000 Subject: [PATCH 46/53] skip flaky suite (#192128) --- x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts index 4bafc3d173156..536935a4b3894 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/ecs_mappings.cy.ts @@ -18,7 +18,8 @@ import { typeInOsqueryFieldInput, } from '../../tasks/live_query'; -describe('EcsMapping', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/192128 +describe.skip('EcsMapping', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'] }, () => { beforeEach(() => { initializeDataViews(); }); From 896b9c09fe6ec061390f7cf249a0227d65454e12 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Wed, 6 Nov 2024 15:04:31 -0500 Subject: [PATCH 47/53] Add InfoSec team members as Ironbank project maintainers (#199148) Elastic's InfoSec organization has agreed to provide justifications to Elastic's IronBank images. We are adding members of that org to Kibana's hardening manifest, listing them as project maintainers. This PR: 1/ Adds select members of the infosec organization 2/ Keeps a few members from Operations and Platform Security, in order to monitor in an observational capacity 3/ Removes engineers who are no longer responsible for the upkeep on this project. 4/ Reorganizes the maintainers list --- .../ironbank/hardening_manifest.yaml | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml index 287739cc03fea..d1d76367b4ec3 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/hardening_manifest.yaml @@ -42,39 +42,47 @@ resources: value: 0ce56bde1853fed3e53282505bac65707385275a27816c29712ab04c187aa249797c82c58759b2b36c210d4e2683eda92359d739a8045cb8385c2c34d37cc9e1 # List of project maintainers maintainers: + # AppEx Operations Members + - email: 'brad.white@elastic.co' + name: 'Brad White' + username: 'brad.white' + cht_member: false - email: 'jon@elastic.co' name: 'Jonathan Budzenski' username: 'jbudz' cht_member: false - - email: 'brad.white@elastic.co' - name: 'Brad White' - username: 'brad.white' + # AppEx Platform Security Members + - email: 'aleh.zasypkin@elastic.co' + name: 'Aleh Zasypkin' + username: 'azasypkin' + cht_member: false + - email: 'larry.gregory@elastic.co' + name: 'Larry Gregory' + username: 'legrego' + cht_member: false + # InfoSec Members + - email: 'abby.zumstein@elastic.co' + name: 'Abby Zumstein' + username: 'azumstein' + cht_member: false + - email: 'arsalan.khan@elastic.co' + name: 'Arsalan Khan' + username: 'khanarsalan' + cht_member: false + - email: 'iaroslava.zhomir@elastic.co' + name: 'Slava Zhomir' + username: 'slava-elastic' + cht_member: false + - email: 'ryan.kam@elastic.co' + name: 'Ryan Kam' + username: 'ryankam' + cht_member: false + - email: 'saumya.shree@elastic.co' + name: 'Saumya Shree' + username: 'shreesaumya' cht_member: false + # CHT Members - email: 'klepal_alexander@bah.com' name: 'Alexander Klepal' username: 'alexander.klepal' - cht_member: true - - cht_member: false - email: larry.gregory@elastic.co - name: Larry Gregory - username: legrego - - cht_member: false - email: aleh.zasypkin@elastic.co - name: Aleh Zasypkin - username: azasypkin - - cht_member: false - email: kurt.greiner@elastic.co - name: Kurt Greiner - username: kc13greiner - - cht_member: false - email: jeramy.soucy@elastic.co - name: Jeramy Soucy - username: jeramysoucy - - cht_member: false - email: sid.mantri@elastic.co - name: Sid Mantri - username: sidmantri - - cht_member: false - email: elena.shostak@elastic.co - name: Elena Shostak - username: elena.shostak + cht_member: true \ No newline at end of file From 05bcf0811892f01c3f637f41df46517d54de70dc Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Wed, 6 Nov 2024 22:11:44 +0100 Subject: [PATCH 48/53] [SecuritySolution] Change copy (#199160) ## Summary Change Entity Store enablement copy because it doesn't make sense for ESS. --- .../entity_store/components/enablement_modal.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx index 974bdddc831e5..6c2528620eb4c 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.tsx @@ -72,20 +72,13 @@ export const EntityStoreEnablementModal: React.FC - - - - Date: Wed, 6 Nov 2024 15:14:14 -0600 Subject: [PATCH 49/53] [Security Solution][Notes] - disable note buttons in the right panel header when in preview mode (#199189) ## Summary This PR fixes a issue where the `Add note` button and the `+` icon button are clickable when an alert is viewed in preview mode. Users should not be able to perform actions here, as the action expands the flyouts and opens the left panel Notes tab, but the issue is the left panel now shows a different alert from the right panel. If the user closes the preview panel, they now see a different alerts on the left and right panels but they have no way to know this. #### Add note button disabled https://github.com/user-attachments/assets/20554b60-39a1-4c6d-b215-e502b5b24dbd #### + button disabled https://github.com/user-attachments/assets/df540aed-b583-457d-a9f4-0093a171ddaa Also adding notes should be disabled when in the rule creation page, as we do not want to generate notes for alerts that actually do not exist yet. To be consistent with the other blocks in the flyout header, we show a `-`. https://github.com/user-attachments/assets/b62ecf85-ee0f-4bee-853c-ff1034b5bf25 --- .../right/components/notes.test.tsx | 89 +++++++++++++++++++ .../right/components/notes.tsx | 70 +++++++++------ 2 files changed, 130 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx index f70cd2aae3e8d..023b0202ecb63 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.test.tsx @@ -23,6 +23,7 @@ import type { Note } from '../../../../../common/api/timeline'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { LeftPanelNotesTab } from '../../left'; +import { getEmptyValue } from '../../../../common/components/empty_value'; jest.mock('@kbn/expandable-flyout'); @@ -43,6 +44,10 @@ jest.mock('react-redux', () => { }); describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should render loading spinner', () => { (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: jest.fn() }); @@ -99,6 +104,34 @@ describe('', () => { }); }); + it('should disabled the Add note button if in preview mode', () => { + const contextValue = { + ...mockContextValue, + isPreviewMode: true, + }; + + const mockOpenLeftPanel = jest.fn(); + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); + + const { getByTestId } = render( + + + + + + ); + + expect(mockDispatch).not.toHaveBeenCalled(); + + const button = getByTestId(NOTES_ADD_NOTE_BUTTON_TEST_ID); + expect(button).toBeInTheDocument(); + expect(button).toBeDisabled(); + + button.click(); + + expect(mockOpenLeftPanel).not.toHaveBeenCalled(); + }); + it('should render number of notes and plus button', () => { const mockOpenLeftPanel = jest.fn(); (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); @@ -135,6 +168,38 @@ describe('', () => { }); }); + it('should disable the plus button if in preview mode', () => { + const mockOpenLeftPanel = jest.fn(); + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: mockOpenLeftPanel }); + + const contextValue = { + ...mockContextValue, + eventId: '1', + isPreviewMode: true, + }; + + const { getByTestId } = render( + + + + + + ); + + expect(getByTestId(NOTES_COUNT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(NOTES_COUNT_TEST_ID)).toHaveTextContent('1'); + + expect(mockDispatch).not.toHaveBeenCalled(); + + const button = getByTestId(NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID); + + expect(button).toBeInTheDocument(); + button.click(); + expect(button).toBeDisabled(); + + expect(mockOpenLeftPanel).not.toHaveBeenCalled(); + }); + it('should render number of notes in scientific notation for big numbers', () => { const createMockNote = (noteId: string): Note => ({ eventId: '1', // should be a valid id based on mockTimelineData @@ -180,6 +245,30 @@ describe('', () => { expect(getByTestId(NOTES_COUNT_TEST_ID)).toHaveTextContent('1k'); }); + it('should show a - when in rule creation workflow', () => { + const contextValue = { + ...mockContextValue, + isPreview: true, + }; + + (useExpandableFlyoutApi as jest.Mock).mockReturnValue({ openLeftPanel: jest.fn() }); + + const { getByText, queryByTestId } = render( + + + + + + ); + + expect(mockDispatch).not.toHaveBeenCalled(); + + expect(queryByTestId(NOTES_ADD_NOTE_ICON_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(NOTES_ADD_NOTE_BUTTON_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(NOTES_COUNT_TEST_ID)).not.toBeInTheDocument(); + expect(getByText(getEmptyValue())).toBeInTheDocument(); + }); + it('should render toast error', () => { const store = createMockStore({ ...mockGlobalState, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx index a981a16117a68..b0e2008c04103 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/notes.tsx @@ -19,6 +19,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { DocumentDetailsLeftPanelKey } from '../../shared/constants/panel_keys'; import { FormattedCount } from '../../../../common/components/formatted_number'; import { useDocumentDetailsContext } from '../../shared/context'; @@ -61,7 +62,7 @@ export const ADD_NOTE_BUTTON = i18n.translate( export const Notes = memo(() => { const { euiTheme } = useEuiTheme(); const dispatch = useDispatch(); - const { eventId, indexName, scopeId } = useDocumentDetailsContext(); + const { eventId, indexName, scopeId, isPreview, isPreviewMode } = useDocumentDetailsContext(); const { addError: addErrorToast } = useAppToasts(); const { openLeftPanel } = useExpandableFlyoutApi(); @@ -80,8 +81,11 @@ export const Notes = memo(() => { ); useEffect(() => { - dispatch(fetchNotesByDocumentIds({ documentIds: [eventId] })); - }, [dispatch, eventId]); + // only fetch notes if we are not in a preview panel, or not in a rule preview workflow + if (!isPreviewMode && !isPreview) { + dispatch(fetchNotesByDocumentIds({ documentIds: [eventId] })); + } + }, [dispatch, eventId, isPreview, isPreviewMode]); const fetchStatus = useSelector((state: State) => selectFetchNotesByDocumentIdsStatus(state)); const fetchError = useSelector((state: State) => selectFetchNotesByDocumentIdsError(state)); @@ -107,37 +111,45 @@ export const Notes = memo(() => { } data-test-subj={NOTES_TITLE_TEST_ID} > - {fetchStatus === ReqStatus.Loading ? ( - + {isPreview ? ( + getEmptyTagValue() ) : ( <> - {notes.length === 0 ? ( - - {ADD_NOTE_BUTTON} - + {fetchStatus === ReqStatus.Loading ? ( + ) : ( - - - - - - + {notes.length === 0 ? ( + - - + data-test-subj={NOTES_ADD_NOTE_BUTTON_TEST_ID} + > + {ADD_NOTE_BUTTON} + + ) : ( + + + + + + + + + )} + )} )} From 4f14c4af416ca5010f9db0ec699c4f8541539f6f Mon Sep 17 00:00:00 2001 From: Rachel Shen Date: Wed, 6 Nov 2024 14:33:25 -0700 Subject: [PATCH 50/53] [Dashboard] Remove modal for inter app from dashboard (#198619) ## Summary Closes https://github.com/elastic/kibana/issues/184257 This PR removes the modal warning users that unsaved changes would be lost when navigating away from dashboard to other apps within Kibana. The modal is not necessary since unsaved changes are saved in session storage. The benefit is that this removes the unnecessary click for users. This does not impact the modal for switching to view mode from edit mode with unsaved changes as shown below. https://github.com/user-attachments/assets/c5bdb0ec-b040-40b0-a511-fd16ad084b90 ### Checklist - [x] Update tests --- .../dashboard_app/_dashboard_app_strings.ts | 15 --------------- .../internal_dashboard_top_nav.tsx | 12 ------------ .../apps/dashboard/group1/edit_visualizations.js | 4 +--- .../plugins/translations/translations/fr-FR.json | 3 --- .../plugins/translations/translations/ja-JP.json | 3 --- .../plugins/translations/translations/zh-CN.json | 3 --- 6 files changed, 1 insertion(+), 39 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts index 8a6e24a183f3a..9c6526ce3403e 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -78,21 +78,6 @@ export const unsavedChangesBadgeStrings = { }), }; -export const leaveConfirmStrings = { - getLeaveTitle: () => - i18n.translate('dashboard.appLeaveConfirmModal.unsavedChangesTitle', { - defaultMessage: 'Unsaved changes', - }), - getLeaveSubtitle: () => - i18n.translate('dashboard.appLeaveConfirmModal.unsavedChangesSubtitle', { - defaultMessage: 'Leave Dashboard with unsaved work?', - }), - getLeaveCancelButtonText: () => - i18n.translate('dashboard.appLeaveConfirmModal.cancelButtonLabel', { - defaultMessage: 'Cancel', - }), -}; - export const getCreateVisualizationButtonTitle = () => i18n.translate('dashboard.solutionToolbar.addPanelButtonLabel', { defaultMessage: 'Create visualization', diff --git a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx index bf2be799dcc1f..47a84b620ede3 100644 --- a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx @@ -33,7 +33,6 @@ import { dashboardManagedBadge, getDashboardBreadcrumb, getDashboardTitle, - leaveConfirmStrings, unsavedChangesBadgeStrings, } from '../dashboard_app/_dashboard_app_strings'; import { useDashboardMountContext } from '../dashboard_app/hooks/dashboard_mount_context'; @@ -48,7 +47,6 @@ import { getDashboardRecentlyAccessedService } from '../services/dashboard_recen import { coreServices, dataService, - embeddableService, navigationService, serverlessService, } from '../services/kibana_services'; @@ -195,16 +193,6 @@ export function InternalDashboardTopNav({ */ useEffect(() => { onAppLeave((actions) => { - if ( - viewMode === 'edit' && - hasUnsavedChanges && - !embeddableService.getStateTransfer().isTransferInProgress - ) { - return actions.confirm( - leaveConfirmStrings.getLeaveSubtitle(), - leaveConfirmStrings.getLeaveTitle() - ); - } return actions.default(); }); return () => { diff --git a/test/functional/apps/dashboard/group1/edit_visualizations.js b/test/functional/apps/dashboard/group1/edit_visualizations.js index f065748f09b00..9e91ed1ea112f 100644 --- a/test/functional/apps/dashboard/group1/edit_visualizations.js +++ b/test/functional/apps/dashboard/group1/edit_visualizations.js @@ -10,7 +10,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { - const { dashboard, header, visualize, common, visEditor } = getPageObjects([ + const { dashboard, header, visualize, visEditor } = getPageObjects([ 'dashboard', 'header', 'visualize', @@ -114,7 +114,6 @@ export default function ({ getService, getPageObjects }) { await header.waitUntilLoadingHasFinished(); await appsMenu.clickLink('Visualize Library'); - await common.clickConfirmOnModal(); expect(await testSubjects.exists('visualizationLandingPage')).to.be(true); }); @@ -132,7 +131,6 @@ export default function ({ getService, getPageObjects }) { await header.waitUntilLoadingHasFinished(); await appsMenu.clickLink('Visualize Library'); - await common.clickConfirmOnModal(); expect(await testSubjects.exists('visualizationLandingPage')).to.be(true); }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3ed262c0aa159..53ddb2e664587 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1209,9 +1209,6 @@ "dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "Maximiser", "dashboard.addPanel.newEmbeddableAddedSuccessMessageTitle": "{savedObjectName} a été ajouté.", "dashboard.addPanel.newEmbeddableWithNoTitleAddedSuccessMessageTitle": "Un panneau a été ajouté", - "dashboard.appLeaveConfirmModal.cancelButtonLabel": "Annuler", - "dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "Quitter Dashboard sans enregistrer ?", - "dashboard.appLeaveConfirmModal.unsavedChangesTitle": "Modifications non enregistrées", "dashboard.badge.readOnly.text": "Lecture seule", "dashboard.badge.readOnly.tooltip": "Impossible d'enregistrer les tableaux de bord", "dashboard.createConfirmModal.cancelButtonLabel": "Annuler", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 16b4b66d842c4..5b646ff86ce3f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1209,9 +1209,6 @@ "dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "最大化", "dashboard.addPanel.newEmbeddableAddedSuccessMessageTitle": "{savedObjectName} が追加されました", "dashboard.addPanel.newEmbeddableWithNoTitleAddedSuccessMessageTitle": "パネルが追加されました", - "dashboard.appLeaveConfirmModal.cancelButtonLabel": "キャンセル", - "dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "作業を保存せずにダッシュボードから移動しますか?", - "dashboard.appLeaveConfirmModal.unsavedChangesTitle": "保存されていない変更", "dashboard.badge.readOnly.text": "読み取り専用", "dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", "dashboard.createConfirmModal.cancelButtonLabel": "キャンセル", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2bf1c18e4f4f6..739a59ef97477 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1209,9 +1209,6 @@ "dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "最大化", "dashboard.addPanel.newEmbeddableAddedSuccessMessageTitle": "{savedObjectName} 已添加", "dashboard.addPanel.newEmbeddableWithNoTitleAddedSuccessMessageTitle": "已添加一个面板", - "dashboard.appLeaveConfirmModal.cancelButtonLabel": "取消", - "dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "离开有未保存工作的仪表板?", - "dashboard.appLeaveConfirmModal.unsavedChangesTitle": "未保存的更改", "dashboard.badge.readOnly.text": "只读", "dashboard.badge.readOnly.tooltip": "无法保存仪表板", "dashboard.createConfirmModal.cancelButtonLabel": "取消", From 8041372868f732530d7fecf1c44fad1f61b631fc Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 6 Nov 2024 17:01:19 -0500 Subject: [PATCH 51/53] [Fleet] Improve input template API yaml comments (#199168) --- .../package_policies_to_agent_inputs.ts | 8 +- .../packages/__fixtures__/docker_2_11_0.ts | 230 ++++++++++++++++++ .../redis_1_18_0_package_info.json | 18 ++ .../redis_1_18_0_streams_template.ts | 9 + .../get_templates_inputs.test.ts.snap | 49 +++- .../epm/packages/get_template_inputs.ts | 138 ++++++++--- .../epm/packages/get_templates_inputs.test.ts | 13 + 7 files changed, 413 insertions(+), 52 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/docker_2_11_0.ts diff --git a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts index 807312fe5e7cb..f2a39c6f8800b 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/package_policies_to_agent_inputs.ts @@ -112,7 +112,8 @@ export const mergeInputsOverrides = ( export const getFullInputStreams = ( input: PackagePolicyInput, - allStreamEnabled: boolean = false + allStreamEnabled: boolean = false, + streamsOriginalIdsMap?: Map // Map of stream ids ): FullAgentPolicyInputStream => { return { ...(input.compiled_input || {}), @@ -121,8 +122,9 @@ export const getFullInputStreams = ( streams: input.streams .filter((stream) => stream.enabled || allStreamEnabled) .map((stream) => { + const streamId = stream.id; const fullStream: FullAgentPolicyInputStream = { - id: stream.id, + id: streamId, data_stream: stream.data_stream, ...stream.compiled_stream, ...Object.entries(stream.config || {}).reduce((acc, [key, { value }]) => { @@ -130,6 +132,8 @@ export const getFullInputStreams = ( return acc; }, {} as { [k: string]: any }), }; + streamsOriginalIdsMap?.set(fullStream.id, streamId); + return fullStream; }), } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/docker_2_11_0.ts b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/docker_2_11_0.ts new file mode 100644 index 0000000000000..67bde1882cfed --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/docker_2_11_0.ts @@ -0,0 +1,230 @@ +/* + * 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 DOCKER_2_11_0_PACKAGE_INFO = { + name: 'docker', + title: 'Docker', + version: '2.11.0', + release: 'ga', + description: 'Collect metrics and logs from Docker instances with Elastic Agent.', + type: 'integration', + download: '/epr/docker/docker-2.11.0.zip', + path: '/package/docker/2.11.0', + icons: [ + { + src: '/img/logo_docker.svg', + path: '/package/docker/2.11.0/img/logo_docker.svg', + title: 'logo docker', + size: '32x32', + type: 'image/svg+xml', + }, + ], + conditions: { + kibana: { + version: '^8.8.0', + }, + }, + owner: { + type: 'elastic', + github: 'elastic/obs-cloudnative-monitoring', + }, + categories: ['observability', 'containers'], + signature_path: '/epr/docker/docker-2.11.0.zip.sig', + format_version: '3.2.2', + readme: '/package/docker/2.11.0/docs/README.md', + license: 'basic', + screenshots: [ + { + src: '/img/docker-overview.png', + path: '/package/docker/2.11.0/img/docker-overview.png', + title: 'Docker Overview', + size: '5120x2562', + type: 'image/png', + }, + ], + assets: [ + '/package/docker/2.11.0/LICENSE.txt', + '/package/docker/2.11.0/changelog.yml', + '/package/docker/2.11.0/manifest.yml', + '/package/docker/2.11.0/docs/README.md', + '/package/docker/2.11.0/img/docker-overview.png', + '/package/docker/2.11.0/img/logo_docker.svg', + '/package/docker/2.11.0/data_stream/container/manifest.yml', + '/package/docker/2.11.0/data_stream/container/sample_event.json', + '/package/docker/2.11.0/data_stream/container_logs/manifest.yml', + '/package/docker/2.11.0/data_stream/container_logs/sample_event.json', + '/package/docker/2.11.0/data_stream/cpu/manifest.yml', + '/package/docker/2.11.0/data_stream/cpu/sample_event.json', + '/package/docker/2.11.0/data_stream/diskio/manifest.yml', + '/package/docker/2.11.0/data_stream/diskio/sample_event.json', + '/package/docker/2.11.0/data_stream/event/manifest.yml', + '/package/docker/2.11.0/data_stream/event/sample_event.json', + '/package/docker/2.11.0/data_stream/healthcheck/manifest.yml', + '/package/docker/2.11.0/data_stream/healthcheck/sample_event.json', + '/package/docker/2.11.0/data_stream/image/manifest.yml', + '/package/docker/2.11.0/data_stream/image/sample_event.json', + '/package/docker/2.11.0/data_stream/info/manifest.yml', + '/package/docker/2.11.0/data_stream/info/sample_event.json', + '/package/docker/2.11.0/data_stream/memory/manifest.yml', + '/package/docker/2.11.0/data_stream/memory/sample_event.json', + '/package/docker/2.11.0/data_stream/network/manifest.yml', + '/package/docker/2.11.0/data_stream/network/sample_event.json', + '/package/docker/2.11.0/kibana/dashboard/docker-AV4REOpp5NkDleZmzKkE.json', + '/package/docker/2.11.0/kibana/search/docker-Metrics-Docker.json', + '/package/docker/2.11.0/data_stream/container/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/container/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/container/fields/fields.yml', + '/package/docker/2.11.0/data_stream/container_logs/fields/agent.yml', + '/package/docker/2.11.0/data_stream/container_logs/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/container_logs/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/container_logs/fields/fields.yml', + '/package/docker/2.11.0/data_stream/cpu/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/cpu/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/cpu/fields/fields.yml', + '/package/docker/2.11.0/data_stream/diskio/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/diskio/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/diskio/fields/fields.yml', + '/package/docker/2.11.0/data_stream/event/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/event/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/event/fields/fields.yml', + '/package/docker/2.11.0/data_stream/healthcheck/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/healthcheck/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/healthcheck/fields/fields.yml', + '/package/docker/2.11.0/data_stream/image/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/image/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/image/fields/fields.yml', + '/package/docker/2.11.0/data_stream/info/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/info/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/info/fields/fields.yml', + '/package/docker/2.11.0/data_stream/memory/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/memory/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/memory/fields/fields.yml', + '/package/docker/2.11.0/data_stream/network/fields/base-fields.yml', + '/package/docker/2.11.0/data_stream/network/fields/ecs.yml', + '/package/docker/2.11.0/data_stream/network/fields/fields.yml', + '/package/docker/2.11.0/data_stream/container/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/container_logs/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/cpu/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/diskio/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/event/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/healthcheck/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/image/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/info/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/memory/agent/stream/stream.yml.hbs', + '/package/docker/2.11.0/data_stream/network/agent/stream/stream.yml.hbs', + ], + policy_templates: [ + { + name: 'docker', + title: 'Docker logs and metrics', + description: 'Collect logs and metrics from Docker instances', + inputs: [ + { + type: 'filestream', + title: 'Collect Docker container logs', + description: 'Collecting docker container logs', + }, + ], + multiple: true, + }, + ], + data_streams: [ + { + type: 'logs', + dataset: 'docker.container_logs', + title: 'Docker container logs', + release: 'ga', + streams: [ + { + input: 'filestream', + vars: [ + { + name: 'paths', + type: 'text', + title: 'Docker container log path', + multi: true, + required: true, + show_user: false, + default: ['/var/lib/docker/containers/${docker.container.id}/*-json.log'], + }, + { + name: 'containerParserStream', + type: 'text', + title: "Container parser's stream configuration", + multi: false, + required: true, + show_user: false, + default: 'all', + }, + { + name: 'condition', + type: 'text', + title: 'Condition', + description: + 'Condition to filter when to apply this datastream. Refer to [Docker provider](https://www.elastic.co/guide/en/fleet/current/docker-provider.html) to find the available keys and to [Conditions](https://www.elastic.co/guide/en/fleet/current/dynamic-input-configuration.html#conditions) on how to use the available keys in conditions.', + multi: false, + required: false, + show_user: true, + }, + { + name: 'additionalParsersConfig', + type: 'yaml', + title: 'Additional parsers configuration', + multi: false, + required: true, + show_user: false, + default: + "# - ndjson:\n# target: json\n# ignore_decoding_error: true\n# - multiline:\n# type: pattern\n# pattern: '^\\['\n# negate: true\n# match: after\n", + }, + { + name: 'processors', + type: 'yaml', + title: 'Processors', + description: + 'Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the events are shipped. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details.', + multi: false, + required: false, + show_user: false, + }, + ], + template_path: 'stream.yml.hbs', + title: 'Collect Docker container logs', + description: 'Collect Docker container logs', + enabled: true, + }, + ], + package: 'docker', + elasticsearch: {}, + path: 'container_logs', + }, + ], +}; + +export const DOCKER_2_11_0_ASSETS_MAP = new Map([ + [ + 'docker-2.11.0/data_stream/container_logs/agent/stream/stream.yml.hbs', + Buffer.from(`id: docker-container-logs-\${docker.container.name}-\${docker.container.id} +paths: +{{#each paths}} + - {{this}} +{{/each}} +{{#if condition}} +condition: {{ condition }} +{{/if}} +parsers: +- container: + stream: {{ containerParserStream }} + format: docker +{{ additionalParsersConfig }} + +{{#if processors}} +processors: +{{processors}} +{{/if}} +`), + ], +]); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json index 57c9b0c68fac9..5aa5c4a878baf 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json +++ b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_package_info.json @@ -139,6 +139,15 @@ "show_user": false, "default": "20s" }, + { + "name": "tags", + "type": "text", + "title": "Tags", + "multi": true, + "required": false, + "show_user": false, + "default": "" + }, { "name": "maxconn", "type": "integer", @@ -203,6 +212,15 @@ { "input": "redis/metrics", "vars": [ + { + "name": "tags_streams", + "type": "text", + "title": "Tags in streams", + "multi": true, + "required": false, + "show_user": false, + "default": "" + }, { "name": "key.patterns", "type": "yaml", diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts index 5ff46f358bbe7..207a48d7132a5 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/__fixtures__/redis_1_18_0_streams_template.ts @@ -76,6 +76,15 @@ period: {{period}} processors: {{processors}} {{/if}} +tags: + - test +{{#each tags as |tag i|}} + - {{tag}} +{{/each}} +tags_streams: +{{#each tags_streams as |tag i|}} + - {{tag}} +{{/each}} `), ], ]); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap index b3a428c0e5a55..56e4f7ee09319 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/packages/__snapshots__/get_templates_inputs.test.ts.snap @@ -8,17 +8,16 @@ exports[`Fleet - getTemplateInputs should work for input package 1`] = ` streams: # Custom log file: Custom log file - id: logfile-log.logs - data_stream: - dataset: - # Dataset name: Set the name for your dataset. Changing the dataset will send the data to a different index. You can't use \`-\` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). - - paths: - - # Log file path: Path to log files to be collected - exclude_files: - - # Exclude files: Patterns to be ignored ignore_older: 72h - tags: - - # Tags: Tags to include in the published event + # data_stream: + # dataset: + # # Dataset name: Set the name for your dataset. Changing the dataset will send the data to a different index. You can't use \`-\` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). + # paths: + # - # Log file path: Path to log files to be collected + # exclude_files: + # - # Exclude files: Patterns to be ignored + # tags: + # - # Tags: Tags to include in the published event " `; @@ -49,8 +48,34 @@ exports[`Fleet - getTemplateInputs should work for integration package 1`] = ` pattern: '*' maxconn: 10 network: tcp - username: # Username - password: # Password period: 10s + tags: + - test + # - # Tags + # username: # Username + # password: # Password + # tags_streams: + # - # Tags in streams +" +`; + +exports[`Fleet - getTemplateInputs should work for package with dynamic ids 1`] = ` +"inputs: + # Collect Docker container logs: Collecting docker container logs + - id: docker-filestream + type: filestream + streams: + # Collect Docker container logs: Collect Docker container logs + - id: docker-container-logs-\${docker.container.name}-\${docker.container.id} + data_stream: + dataset: docker.container_logs + type: logs + paths: + - /var/lib/docker/containers/\${docker.container.id}/*-json.log + parsers: + - container: + stream: all + format: docker + # condition: # Condition: Condition to filter when to apply this datastream. Refer to [Docker provider](https://www.elastic.co/guide/en/fleet/current/docker-provider.html) to find the available keys and to [Conditions](https://www.elastic.co/guide/en/fleet/current/dynamic-input-configuration.html#conditions) on how to use the available keys in conditions. " `; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts index 8c63f4b093dd0..f76b739904f3f 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_template_inputs.ts @@ -51,20 +51,31 @@ type PackageWithInputAndStreamIndexed = Record< // Function based off storedPackagePolicyToAgentInputs, it only creates the `streams` section instead of the FullAgentPolicyInput export const templatePackagePolicyToFullInputStreams = ( - packagePolicyInputs: PackagePolicyInput[] + packagePolicyInputs: PackagePolicyInput[], + inputAndStreamsIdsMap?: Map }> ): TemplateAgentPolicyInput[] => { const fullInputsStreams: TemplateAgentPolicyInput[] = []; if (!packagePolicyInputs || packagePolicyInputs.length === 0) return fullInputsStreams; packagePolicyInputs.forEach((input) => { + const streamsIdsMap = new Map(); + + const inputId = input.policy_template + ? `${input.policy_template}-${input.type}` + : `${input.type}`; const fullInputStream = { // @ts-ignore-next-line the following id is actually one level above the one in fullInputStream, but the linter thinks it gets overwritten - id: input.policy_template ? `${input.policy_template}-${input.type}` : `${input.type}`, + id: inputId, type: input.type, - ...getFullInputStreams(input, true), + ...getFullInputStreams(input, true, streamsIdsMap), }; + inputAndStreamsIdsMap?.set(fullInputStream.id, { + originalId: inputId, + streams: streamsIdsMap, + }); + // deeply merge the input.config values with the full policy input stream merge( fullInputStream, @@ -167,8 +178,13 @@ export async function getTemplateInputs( ...emptyPackagePolicy, inputs: compiledInputs, }; + const inputIdsDestinationMap = new Map< + string, + { originalId: string; streams: Map } + >(); const inputs = templatePackagePolicyToFullInputStreams( - packagePolicyWithInputs.inputs as PackagePolicyInput[] + packagePolicyWithInputs.inputs as PackagePolicyInput[], + inputIdsDestinationMap ); if (format === 'json') { @@ -181,7 +197,7 @@ export async function getTemplateInputs( sortKeys: _sortYamlKeys, } ); - return addCommentsToYaml(yaml, buildIndexedPackage(packageInfo)); + return addCommentsToYaml(yaml, buildIndexedPackage(packageInfo), inputIdsDestinationMap); } return { inputs: [] }; @@ -247,7 +263,8 @@ function buildIndexedPackage(packageInfo: PackageInfo): PackageWithInputAndStrea function addCommentsToYaml( yaml: string, - packageIndexInputAndStreams: PackageWithInputAndStreamIndexed + packageIndexInputAndStreams: PackageWithInputAndStreamIndexed, + inputIdsDestinationMap: Map }> ) { const doc = yamlDoc.parseDocument(yaml); // Add input and streams comments @@ -261,28 +278,16 @@ function addCommentsToYaml( if (!yamlDoc.isScalar(inputIdNode)) { return; } - const inputId = inputIdNode.value as string; + const inputId = + inputIdsDestinationMap.get(inputIdNode.value as string)?.originalId ?? + (inputIdNode.value as string); const pkgInput = packageIndexInputAndStreams[inputId]; if (pkgInput) { inputItem.commentBefore = ` ${pkgInput.title}${ pkgInput.description ? `: ${pkgInput.description}` : '' }`; - yamlDoc.visit(inputItem, { - Scalar(key, node) { - if (node.value) { - const val = node.value.toString(); - for (const varDef of pkgInput.vars ?? []) { - const placeholder = getPlaceholder(varDef); - if (val.includes(placeholder)) { - node.comment = ` ${varDef.title}${ - varDef.description ? `: ${varDef.description}` : '' - }`; - } - } - } - }, - }); + commentVariablesInYaml(inputItem, pkgInput.vars ?? []); const yamlStreams = inputItem.get('streams'); if (!yamlDoc.isCollection(yamlStreams)) { @@ -294,27 +299,16 @@ function addCommentsToYaml( } const streamIdNode = streamItem.get('id', true); if (yamlDoc.isScalar(streamIdNode)) { - const streamId = streamIdNode.value as string; + const streamId = + inputIdsDestinationMap + .get(inputIdNode.value as string) + ?.streams?.get(streamIdNode.value as string) ?? (streamIdNode.value as string); const pkgStream = pkgInput.streams[streamId]; if (pkgStream) { streamItem.commentBefore = ` ${pkgStream.title}${ pkgStream.description ? `: ${pkgStream.description}` : '' }`; - yamlDoc.visit(streamItem, { - Scalar(key, node) { - if (node.value) { - const val = node.value.toString(); - for (const varDef of pkgStream.vars ?? []) { - const placeholder = getPlaceholder(varDef); - if (val.includes(placeholder)) { - node.comment = ` ${varDef.title}${ - varDef.description ? `: ${varDef.description}` : '' - }`; - } - } - } - }, - }); + commentVariablesInYaml(streamItem, pkgStream.vars ?? []); } } }); @@ -324,3 +318,71 @@ function addCommentsToYaml( return doc.toString(); } + +function commentVariablesInYaml(rootNode: yamlDoc.Node, vars: RegistryVarsEntry[] = []) { + // Node need to be deleted after the end of the visit to be able to visit every node + const toDeleteFn: Array<() => void> = []; + yamlDoc.visit(rootNode, { + Scalar(key, node, path) { + if (node.value) { + const val = node.value.toString(); + for (const varDef of vars) { + const placeholder = getPlaceholder(varDef); + if (val.includes(placeholder)) { + node.comment = ` ${varDef.title}${varDef.description ? `: ${varDef.description}` : ''}`; + + const paths = [...path].reverse(); + + let prevPart: yamlDoc.Node | yamlDoc.Document | yamlDoc.Pair = node; + + for (const pathPart of paths) { + if (yamlDoc.isCollection(pathPart)) { + // If only one items in the collection comment the whole collection + if (pathPart.items.length === 1) { + continue; + } + } + if (yamlDoc.isSeq(pathPart)) { + const commentDoc = new yamlDoc.Document(new yamlDoc.YAMLSeq()); + commentDoc.add(prevPart); + const commentStr = commentDoc.toString().trimEnd(); + pathPart.comment = pathPart.comment + ? `${pathPart.comment} ${commentStr}` + : ` ${commentStr}`; + const keyToDelete = prevPart; + + toDeleteFn.push(() => { + pathPart.items.forEach((item, index) => { + if (item === keyToDelete) { + pathPart.delete(new yamlDoc.Scalar(index)); + } + }); + }); + return; + } + + if (yamlDoc.isMap(pathPart)) { + if (yamlDoc.isPair(prevPart)) { + const commentDoc = new yamlDoc.Document(new yamlDoc.YAMLMap()); + commentDoc.add(prevPart); + const commentStr = commentDoc.toString().trimEnd(); + + pathPart.comment = pathPart.comment + ? `${pathPart.comment}\n ${commentStr.toString()}` + : ` ${commentStr.toString()}`; + const keyToDelete = prevPart.key; + toDeleteFn.push(() => pathPart.delete(keyToDelete)); + } + return; + } + + prevPart = pathPart; + } + } + } + } + }, + }); + + toDeleteFn.forEach((deleteFn) => deleteFn()); +} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts index 087002f212852..1a3738d8eaa82 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_templates_inputs.test.ts @@ -16,6 +16,7 @@ import REDIS_1_18_0_PACKAGE_INFO from './__fixtures__/redis_1_18_0_package_info. import { getPackageAssetsMap, getPackageInfo } from './get'; import { REDIS_ASSETS_MAP } from './__fixtures__/redis_1_18_0_streams_template'; import { LOGS_2_3_0_ASSETS_MAP, LOGS_2_3_0_PACKAGE_INFO } from './__fixtures__/logs_2_3_0'; +import { DOCKER_2_11_0_PACKAGE_INFO, DOCKER_2_11_0_ASSETS_MAP } from './__fixtures__/docker_2_11_0'; jest.mock('./get'); @@ -41,6 +42,7 @@ packageInfoCache.set('limited_package-0.0.0', { packageInfoCache.set('redis-1.18.0', REDIS_1_18_0_PACKAGE_INFO); packageInfoCache.set('log-2.3.0', LOGS_2_3_0_PACKAGE_INFO); +packageInfoCache.set('docker-2.11.0', DOCKER_2_11_0_PACKAGE_INFO); describe('Fleet - templatePackagePolicyToFullInputStreams', () => { const mockInput: PackagePolicyInput = { @@ -330,6 +332,9 @@ describe('Fleet - getTemplateInputs', () => { if (packageInfo.name === 'log') { return LOGS_2_3_0_ASSETS_MAP; } + if (packageInfo.name === 'docker') { + return DOCKER_2_11_0_ASSETS_MAP; + } return new Map(); }); @@ -350,6 +355,14 @@ describe('Fleet - getTemplateInputs', () => { expect(template).toMatchSnapshot(); }); + it('should work for package with dynamic ids', async () => { + const soMock = savedObjectsClientMock.create(); + soMock.get.mockResolvedValue({ attributes: {} } as any); + const template = await getTemplateInputs(soMock, 'docker', '2.11.0', 'yml'); + + expect(template).toMatchSnapshot(); + }); + it('should work for input package', async () => { const soMock = savedObjectsClientMock.create(); soMock.get.mockResolvedValue({ attributes: {} } as any); From 09dd66d355fbb32fa0090e36945819a9509138c6 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:57:55 -0600 Subject: [PATCH 52/53] [Security Solution] Update rule link in cases activities (#198836) ## Summary As a follow up to https://github.com/elastic/kibana/pull/191764, this PR updates rule names in `Activity` tab in cases to open a rule flyout instead of going to rule details page. **Security solution changes**: replace rule page navigation with a `openFlyout` call **Cases plugin changes**: update the rules link component to accept either `href` or `onClick`. ![image](https://github.com/user-attachments/assets/c5dca885-61b8-4481-adfa-f9e615a01265) ### Checklist - [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 --- .../cases/public/components/links/index.tsx | 2 +- .../user_actions/comment/alert_event.test.tsx | 63 ++++++++++++++++--- .../user_actions/comment/alert_event.tsx | 10 ++- .../public/cases/pages/index.tsx | 33 ++++------ 4 files changed, 75 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/cases/public/components/links/index.tsx b/x-pack/plugins/cases/public/components/links/index.tsx index f1e8ca5cdb4af..9b610db63ed10 100644 --- a/x-pack/plugins/cases/public/components/links/index.tsx +++ b/x-pack/plugins/cases/public/components/links/index.tsx @@ -12,7 +12,7 @@ import { useCaseViewNavigation, useConfigureCasesNavigation } from '../../common import * as i18n from './translations'; export interface CasesNavigation { - href: K extends 'configurable' ? (arg: T) => string : string; + href?: K extends 'configurable' ? (arg: T) => string : string; onClick: K extends 'configurable' ? (arg: T, arg2: React.MouseEvent | MouseEvent) => Promise | void : (arg: T) => Promise | void; diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.test.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.test.tsx index 60d5759de6e21..d060f1d9d71ac 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.test.tsx @@ -47,10 +47,38 @@ describe('Alert events', () => { expect(wrapper.text()).toBe('added an alert from Awesome rule'); }); - it('does NOT render the link when the rule id is null', async () => { + it('renders the link when onClick is provided but href is not valid', async () => { const wrapper = mount( - + + + ); + + expect( + wrapper.find(`[data-test-subj="alert-rule-link-action-id-1"]`).first().exists() + ).toBeTruthy(); + }); + + it('renders the link when href is valid but onClick is not available', async () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find(`[data-test-subj="alert-rule-link-action-id-1"]`).first().exists() + ).toBeTruthy(); + }); + + it('does NOT render the link when the href and onclick are invalid but it shows the rule name', async () => { + const wrapper = mount( + + ); @@ -61,10 +89,10 @@ describe('Alert events', () => { expect(wrapper.text()).toBe('added an alert from Awesome rule'); }); - it('does NOT render the link when the href is invalid but it shows the rule name', async () => { + it('does NOT render the link when the rule id is null', async () => { const wrapper = mount( - + ); @@ -131,9 +159,28 @@ describe('Alert events', () => { expect(result.getByTestId('alert-rule-link-action-id-1')).toHaveTextContent('Awesome rule'); }); - it('does NOT render the link when the rule id is null', async () => { + it('renders the link when onClick is provided but href is not valid', async () => { const result = appMock.render( - + + ); + expect(result.getByTestId('alert-rule-link-action-id-1')).toHaveTextContent('Awesome rule'); + }); + + it('renders the link when href is valid but onClick is not available', async () => { + const result = appMock.render( + + ); + expect(result.getByTestId('alert-rule-link-action-id-1')).toHaveTextContent('Awesome rule'); + }); + + it('does NOT render the link when the href and onclick are invalid but it shows the rule name', async () => { + const result = appMock.render( + ); expect(result.getByTestId('multiple-alerts-user-action-action-id-1')).toHaveTextContent( @@ -142,9 +189,9 @@ describe('Alert events', () => { expect(result.queryByTestId('alert-rule-link-action-id-1')).toBeFalsy(); }); - it('does NOT render the link when the href is invalid but it shows the rule name', async () => { + it('does NOT render the link when the rule id is null', async () => { const result = appMock.render( - + ); expect(result.getByTestId('multiple-alerts-user-action-action-id-1')).toHaveTextContent( diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.tsx index 42e5e6b9d4427..a8ffbb987a021 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/alert_event.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useCallback } from 'react'; +import React, { memo, useCallback, useMemo } from 'react'; import { isEmpty } from 'lodash'; import { EuiLoadingSpinner } from '@elastic/eui'; @@ -38,12 +38,18 @@ const RuleLink: React.FC = memo( const ruleDetailsHref = getRuleDetailsHref?.(ruleId); const finalRuleName = ruleName ?? i18n.UNKNOWN_RULE; + const isValidLink = useMemo(() => { + if (!onRuleDetailsClick && !ruleDetailsHref) { + return false; + } + return !isEmpty(ruleId); + }, [onRuleDetailsClick, ruleDetailsHref, ruleId]); if (loadingAlertData) { return ; } - if (!isEmpty(ruleId) && ruleDetailsHref != null) { + if (isValidLink) { return ( { const { getAppUrl, navigateTo } = useNavigation(); const userCasesPermissions = cases.helpers.canUseCases([APP_ID]); const dispatch = useDispatch(); - const { formatUrl: detectionsFormatUrl, search: detectionsUrlSearch } = useFormatUrl( - SecurityPageName.rules - ); const { openFlyout } = useExpandableFlyoutApi(); - const getDetectionsRuleDetailsHref = useCallback( - (ruleId: string | null | undefined) => - detectionsFormatUrl(getRuleDetailsUrl(ruleId ?? '', detectionsUrlSearch)), - [detectionsFormatUrl, detectionsUrlSearch] - ); - const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions'); const showAlertDetails = useCallback( @@ -71,6 +60,15 @@ const CaseContainerComponent: React.FC = () => { [openFlyout, telemetry] ); + const onRuleDetailsClick = useCallback( + (ruleId: string | null | undefined) => { + if (ruleId) { + openFlyout({ right: { id: RulePanelKey, params: { ruleId } } }); + } + }, + [openFlyout] + ); + const { onLoad: onAlertsTableLoaded } = useFetchNotes(); const endpointDetailsHref = (endpointId: string) => @@ -138,16 +136,7 @@ const CaseContainerComponent: React.FC = () => { }, }, ruleDetailsNavigation: { - href: getDetectionsRuleDetailsHref, - onClick: async (ruleId: string | null | undefined, e) => { - if (e) { - e.preventDefault(); - } - return navigateTo({ - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsUrl(ruleId ?? ''), - }); - }, + onClick: onRuleDetailsClick, }, showAlertDetails, timelineIntegration: { From e3994831ff3c6f337a209bcd3d2f93344eb787a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Thu, 7 Nov 2024 00:25:30 +0100 Subject: [PATCH 53/53] Enable summarisation spec (#199134) This enables the api test that covers summarisation. It was originally skipped because tiny_elser was not available on CI. Now it is we can effectively test storing and retrieving entries from the knowledge base. --- .../server/plugin.ts | 5 ---- .../complete/functions/summarize.spec.ts | 30 +++++++++++++++++-- 2 files changed, 27 insertions(+), 8 deletions(-) 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 81f4e24d4d21f..3bdff9eb17606 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 @@ -32,7 +32,6 @@ import { ObservabilityAIAssistantPluginSetupDependencies, ObservabilityAIAssistantPluginStartDependencies, } from './types'; -import { addLensDocsToKb } from './service/knowledge_base_service/kb_docs/lens'; import { registerFunctions } from './functions'; import { recallRankingEvent } from './analytics/recall_ranking'; import { initLangtrace } from './service/client/instrumentation/init_langtrace'; @@ -164,10 +163,6 @@ export class ObservabilityAIAssistantPlugin service.register(registerFunctions); - if (this.config.enableKnowledgeBase) { - addLensDocsToKb({ service, logger: this.logger.get('kb').get('lens') }); - } - registerServerRoutes({ core, logger: this.logger, diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/summarize.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/summarize.spec.ts index 238be31220aa9..923e8b0206070 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/summarize.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/functions/summarize.spec.ts @@ -14,18 +14,32 @@ import { createProxyActionConnector, deleteActionConnector, } from '../../../common/action_connectors'; +import { + clearKnowledgeBase, + createKnowledgeBaseModel, + deleteKnowledgeBaseModel, +} from '../../knowledge_base/helpers'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const log = getService('log'); + const ml = getService('ml'); + const es = getService('es'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); - // Skipped until Elser is available in tests - describe.skip('when calling summarize function', () => { + describe('when calling summarize function', () => { let proxy: LlmProxy; let connectorId: string; before(async () => { + await clearKnowledgeBase(es); + await createKnowledgeBaseModel(ml); + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + }) + .expect(200); + proxy = await createLlmProxy(log); connectorId = await createProxyActionConnector({ supertest, log, port: proxy.getPort() }); @@ -44,7 +58,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { id: 'my-id', text: 'Hello world', is_correction: false, - confidence: 1, + confidence: 'high', public: false, }), }, @@ -55,7 +69,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { proxy.close(); + await deleteActionConnector({ supertest, connectorId, log }); + await deleteKnowledgeBaseModel(ml); }); it('persists entry in knowledge base', async () => { @@ -70,6 +86,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }); + const { role, public: isPublic, text, type, user, id } = res.body.entries[0]; + + expect(role).to.eql('assistant_summarization'); + expect(isPublic).to.eql(false); + expect(text).to.eql('Hello world'); + expect(type).to.eql('contextual'); + expect(user?.name).to.eql('editor'); + expect(id).to.eql('my-id'); expect(res.body.entries).to.have.length(1); }); });