From 5f310f773abcd850e739a712082fb188378bb014 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 21 Aug 2023 16:48:08 +0200 Subject: [PATCH 01/29] Unskip X-Pack Saved Object Tagging Functional Tests (#164273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary close https://github.com/elastic/kibana/issues/88639 10 🟢 runs https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2926 40 🟢 runs https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2928 --- .../functional/tests/visualize_integration.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts index 515c79ae5156a..a7a03a58ba0cf 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/visualize_integration.ts @@ -61,8 +61,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); }; - // Failing: See https://github.com/elastic/kibana/issues/88639 - describe.skip('visualize integration', () => { + describe('visualize integration', () => { before(async () => { // clean up any left-over visualizations and tags from tests that didn't clean up after themselves await kibanaServer.savedObjects.clean({ types: ['tag', 'visualization'] }); From 98a135cc7aba034592e77a3c0a0c9caed6c0615a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:08:47 -0400 Subject: [PATCH 02/29] skip failing test suite (#164318) --- .../group4/telemetry/task_based/detection_rules.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts index 686596b25e008..789d653f5f3aa 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts @@ -34,7 +34,8 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const retry = getService('retry'); - describe('Detection rule task telemetry', async () => { + // Failing: See https://github.com/elastic/kibana/issues/164318 + describe.skip('Detection rule task telemetry', async () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/telemetry'); }); From c328d2da218f4da46c73a0e5823e434385c179ca Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 21 Aug 2023 08:10:35 -0700 Subject: [PATCH 03/29] [Reporting] Adjust export type conditionals in server startup (#164232) ## Summary This PR cleans up how config settings are used as conditionals in the Reporting plugin startup phase. The existing code is correct, but it's heavily aligned to certain business requirements that might not be understood by a reader. The change in the PR uses simpler conditionals that are separated from internal business decisions. The result should be clearer readability of the code. --- .../common/constants/report_types.ts | 2 +- .../reporting_panel_content.tsx | 4 +- x-pack/plugins/reporting/server/core.ts | 21 ++-- .../plugins/reporting/server/plugin.test.ts | 97 +++++++++++-------- .../create_mock_reportingplugin.ts | 1 + 5 files changed, 76 insertions(+), 49 deletions(-) diff --git a/x-pack/plugins/reporting/common/constants/report_types.ts b/x-pack/plugins/reporting/common/constants/report_types.ts index 4e9b945aadb72..b66e00de7407f 100644 --- a/x-pack/plugins/reporting/common/constants/report_types.ts +++ b/x-pack/plugins/reporting/common/constants/report_types.ts @@ -6,7 +6,7 @@ */ // Export Type Definitions -export const CSV_REPORT_TYPE = 'CSV'; +export const CSV_REPORT_TYPE = 'csv_searchsource'; export const CSV_REPORT_TYPE_V2 = 'csv_v2'; export const PDF_REPORT_TYPE = 'printablePdf'; diff --git a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx index 2252694fcaa97..58358d33f5b1f 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.tsx @@ -251,8 +251,8 @@ class ReportingPanelContentUi extends Component { case PDF_REPORT_TYPE: case PDF_REPORT_TYPE_V2: return 'PDF'; - case 'csv_searchsource': - return CSV_REPORT_TYPE; + case CSV_REPORT_TYPE: + return 'csv'; case 'png': case PNG_REPORT_TYPE_V2: return PNG_REPORT_TYPE; diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts index d81f7b8ab9808..453940f3cc914 100644 --- a/x-pack/plugins/reporting/server/core.ts +++ b/x-pack/plugins/reporting/server/core.ts @@ -226,22 +226,27 @@ export class ReportingCore { * only CSV export types should be registered in the export types registry for serverless */ private getExportTypes(): ExportType[] { + const { csv, pdf, png } = this.config.export_types; const exportTypes = []; - if (!this.config.export_types.pdf.enabled || !this.config.export_types.png.enabled) { - exportTypes.push( - new CsvSearchSourceExportType(this.core, this.config, this.logger, this.context) - ); - exportTypes.push(new CsvV2ExportType(this.core, this.config, this.logger, this.context)); - } else { + + if (csv.enabled) { + // NOTE: CsvSearchSourceExportType should be deprecated and replaced with V2 in the UI: https://github.com/elastic/kibana/issues/151190 exportTypes.push( new CsvSearchSourceExportType(this.core, this.config, this.logger, this.context) ); exportTypes.push(new CsvV2ExportType(this.core, this.config, this.logger, this.context)); + } + + if (pdf.enabled) { + // NOTE: PdfV1ExportType is deprecated and tagged for removal: https://github.com/elastic/kibana/issues/154601 + exportTypes.push(new PdfV1ExportType(this.core, this.config, this.logger, this.context)); exportTypes.push(new PdfExportType(this.core, this.config, this.logger, this.context)); + } + + if (png.enabled) { exportTypes.push(new PngExportType(this.core, this.config, this.logger, this.context)); - // deprecated export types for tests - exportTypes.push(new PdfV1ExportType(this.core, this.config, this.logger, this.context)); } + return exportTypes; } diff --git a/x-pack/plugins/reporting/server/plugin.test.ts b/x-pack/plugins/reporting/server/plugin.test.ts index 176b648b9d02a..3e57e3bf3322b 100644 --- a/x-pack/plugins/reporting/server/plugin.test.ts +++ b/x-pack/plugins/reporting/server/plugin.test.ts @@ -7,8 +7,15 @@ import type { CoreSetup, CoreStart, Logger } from '@kbn/core/server'; import { coreMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { PDF_REPORT_TYPE_V2, PNG_REPORT_TYPE_V2 } from '../common/constants/report_types'; +import { + CSV_REPORT_TYPE, + CSV_REPORT_TYPE_V2, + PDF_REPORT_TYPE, + PDF_REPORT_TYPE_V2, + PNG_REPORT_TYPE_V2, +} from '../common/constants'; import type { ReportingCore, ReportingInternalStart } from './core'; +import { ExportTypesRegistry } from './lib/export_types_registry'; import { ReportingPlugin } from './plugin'; import { createMockConfigSchema, @@ -30,6 +37,8 @@ describe('Reporting Plugin', () => { let plugin: ReportingPlugin; beforeEach(async () => { + jest.clearAllMocks(); + configSchema = createMockConfigSchema(); initContext = coreMock.createPluginInitializerContext(configSchema); coreSetup = coreMock.createSetup(configSchema); @@ -81,50 +90,62 @@ describe('Reporting Plugin', () => { `); expect(logger.error).toHaveBeenCalledTimes(2); }); - describe('config and export types registry validation', () => { - it('expect image reporting to be in registry by default', async () => { - // wait for the setup phase background work - plugin.setup(coreSetup, pluginSetup); - await new Promise(setImmediate); - - // create a way for an error to happen - const reportingCore = (plugin as unknown as { reportingCore: ReportingCore }).reportingCore; - - // wait for the startup phase background work - plugin.start(coreStart, pluginStart); - await new Promise(setImmediate); - expect(reportingCore.getExportTypesRegistry().getById(PDF_REPORT_TYPE_V2)).toHaveProperty( - 'id', - PDF_REPORT_TYPE_V2 - ); - expect(reportingCore.getExportTypesRegistry().getById(PNG_REPORT_TYPE_V2)).toHaveProperty( - 'id', - PNG_REPORT_TYPE_V2 - ); + + describe('config and export types registration', () => { + jest.mock('./lib/export_types_registry'); + ExportTypesRegistry.prototype.getAll = jest.fn(() => []); // code breaks if getAll returns undefined + let registerSpy: jest.SpyInstance; + + beforeEach(async () => { + registerSpy = jest.spyOn(ExportTypesRegistry.prototype, 'register'); + pluginSetup = createMockPluginSetup({}) as unknown as ReportingSetupDeps; + pluginStart = await createMockPluginStart(coreStart, configSchema); + plugin = new ReportingPlugin(initContext); + }); + + it('expect all report types to be in registry', async () => { + // check the spy function + expect(registerSpy).toHaveBeenCalledTimes(5); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: CSV_REPORT_TYPE })); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: CSV_REPORT_TYPE_V2 })); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: PDF_REPORT_TYPE })); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: PDF_REPORT_TYPE_V2 })); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: PNG_REPORT_TYPE_V2 })); }); - it('expect pdf to not be in registry if config does not enable it', async () => { - configSchema = { ...createMockConfigSchema(), export_types: { pdf: { enabled: false } } }; + + it('expect image report types not to be in registry if disabled', async () => { + jest.clearAllMocks(); + + configSchema = createMockConfigSchema({ + export_types: { + csv: { enabled: true }, + pdf: { enabled: false }, + png: { enabled: false }, + }, + }); + initContext = coreMock.createPluginInitializerContext(configSchema); coreSetup = coreMock.createSetup(configSchema); coreStart = coreMock.createStart(); pluginSetup = createMockPluginSetup({}) as unknown as ReportingSetupDeps; pluginStart = await createMockPluginStart(coreStart, configSchema); - plugin = new ReportingPlugin(initContext); - // wait for the setup phase background work - plugin.setup(coreSetup, pluginSetup); - await new Promise(setImmediate); - - // create a way for an error to happen - const reportingCore = (plugin as unknown as { reportingCore: ReportingCore }).reportingCore; - - // wait for the startup phase background work - plugin.start(coreStart, pluginStart); - await new Promise(setImmediate); - const checkPdf = () => reportingCore.getExportTypesRegistry().getById(PDF_REPORT_TYPE_V2); - const checkPng = () => reportingCore.getExportTypesRegistry().getById(PNG_REPORT_TYPE_V2); - expect(checkPdf).toThrowError(`Unknown id ${PDF_REPORT_TYPE_V2}`); - expect(checkPng).toThrowError(`Unknown id ${PNG_REPORT_TYPE_V2}`); + + // check the spy function was called with CSV + expect(registerSpy).toHaveBeenCalledTimes(2); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: CSV_REPORT_TYPE })); + expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ id: CSV_REPORT_TYPE_V2 })); + + // check the spy function was NOT called with anything else + expect(registerSpy).not.toHaveBeenCalledWith( + expect.objectContaining({ id: PDF_REPORT_TYPE }) + ); + expect(registerSpy).not.toHaveBeenCalledWith( + expect.objectContaining({ id: PDF_REPORT_TYPE_V2 }) + ); + expect(registerSpy).not.toHaveBeenCalledWith( + expect.objectContaining({ id: PNG_REPORT_TYPE_V2 }) + ); }); }); }); diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts index 4fbacb3a8b994..38a7bb8399abb 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_reportingplugin.ts @@ -115,6 +115,7 @@ export const createMockConfigSchema = ( pdf: { enabled: true }, png: { enabled: true }, csv: { enabled: true }, + ...overrides.export_types, }, } as ReportingConfigType; }; From c1d2834f1aa675f2682793d83ac9ad07c3854990 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 21 Aug 2023 09:29:08 -0700 Subject: [PATCH 04/29] [OAS] Remove redundant connector properties (#163987) --- .../api-generated/connectors/connector-apis-passthru.asciidoc | 4 ++-- x-pack/plugins/actions/docs/openapi/bundled.json | 4 ---- x-pack/plugins/actions/docs/openapi/bundled.yaml | 4 ---- .../schemas/update_connector_request_slack_api.yaml | 2 -- .../schemas/update_connector_request_slack_webhook.yaml | 2 -- 5 files changed, 2 insertions(+), 14 deletions(-) diff --git a/docs/api-generated/connectors/connector-apis-passthru.asciidoc b/docs/api-generated/connectors/connector-apis-passthru.asciidoc index a31c0c5fa963f..64bf78a2b66ed 100644 --- a/docs/api-generated/connectors/connector-apis-passthru.asciidoc +++ b/docs/api-generated/connectors/connector-apis-passthru.asciidoc @@ -2475,7 +2475,7 @@ Any modifications made to this file will be overwritten.
name
String The display name for the connector.
-
secrets
+
secrets
@@ -2483,7 +2483,7 @@ Any modifications made to this file will be overwritten.
name
String The display name for the connector.
-
secrets
+
secrets
diff --git a/x-pack/plugins/actions/docs/openapi/bundled.json b/x-pack/plugins/actions/docs/openapi/bundled.json index 27606af5b7d20..59ca423963caf 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.json +++ b/x-pack/plugins/actions/docs/openapi/bundled.json @@ -3720,8 +3720,6 @@ "description": "The display name for the connector." }, "secrets": { - "type": "object", - "description": "The secrets object containing the necessary fields for authentication.", "$ref": "#/components/schemas/secrets_properties_slack_api" } } @@ -3739,8 +3737,6 @@ "description": "The display name for the connector." }, "secrets": { - "type": "object", - "description": "The secrets object containing the necessary fields for authentication.", "$ref": "#/components/schemas/secrets_properties_slack_webhook" } } diff --git a/x-pack/plugins/actions/docs/openapi/bundled.yaml b/x-pack/plugins/actions/docs/openapi/bundled.yaml index a784d60473b09..b090dba90a87a 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.yaml +++ b/x-pack/plugins/actions/docs/openapi/bundled.yaml @@ -2549,8 +2549,6 @@ components: type: string description: The display name for the connector. secrets: - type: object - description: The secrets object containing the necessary fields for authentication. $ref: '#/components/schemas/secrets_properties_slack_api' update_connector_request_slack_webhook: title: Update Slack connector request @@ -2563,8 +2561,6 @@ components: type: string description: The display name for the connector. secrets: - type: object - description: The secrets object containing the necessary fields for authentication. $ref: '#/components/schemas/secrets_properties_slack_webhook' update_connector_request_swimlane: title: Update Swimlane connector request diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_api.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_api.yaml index e85a0436a035b..1a0c99c1847a0 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_api.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_api.yaml @@ -8,6 +8,4 @@ properties: type: string description: The display name for the connector. secrets: - type: object - description: The secrets object containing the necessary fields for authentication. $ref: 'secrets_properties_slack_api.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_webhook.yaml index 4ed93f519f3c2..67b0f9bb310ad 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_slack_webhook.yaml @@ -8,6 +8,4 @@ properties: type: string description: The display name for the connector. secrets: - type: object - description: The secrets object containing the necessary fields for authentication. $ref: 'secrets_properties_slack_webhook.yaml' From 9cff5fc8440892a45cb5482ab5baf8ba2bb92a09 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:54:39 -0400 Subject: [PATCH 05/29] skip failing test suite (#164334) --- .../group4/telemetry/task_based/security_lists.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts index daf5e6e8a1c74..50661a537472d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/security_lists.ts @@ -31,7 +31,8 @@ export default ({ getService }: FtrProviderContext) => { const retry = getService('retry'); const es = getService('es'); - describe('Security lists task telemetry', async () => { + // Failing: See https://github.com/elastic/kibana/issues/164334 + describe.skip('Security lists task telemetry', async () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/telemetry'); }); From 654de7b7285ad5748dc9ff1e19156e753d68d568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Mon, 21 Aug 2023 20:10:30 +0200 Subject: [PATCH 06/29] [APM] Swallow unhandled exceptions (#164251) If an unhandled error occurs, it will break the diagnostics bundle. This change will log errors and then swallow them. This should go out in 8.10 since it is already affecting users. --- .../app/diagnostics/indices_tab.tsx | 2 +- .../summary_tab/indicies_status.tsx | 3 +- .../get_index_templates_by_index_pattern.ts | 4 +- .../diagnostics/get_diagnostics_bundle.ts | 91 +++++++++---------- ..._403_exception.ts => handle_exceptions.ts} | 9 +- .../apm/server/routes/diagnostics/route.ts | 10 +- 6 files changed, 56 insertions(+), 63 deletions(-) rename x-pack/plugins/apm/server/routes/diagnostics/helpers/{handle_403_exception.ts => handle_exceptions.ts} (81%) diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/indices_tab.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/indices_tab.tsx index 4cf9ea9cf8635..56d9cc1b472a2 100644 --- a/x-pack/plugins/apm/public/components/app/diagnostics/indices_tab.tsx +++ b/x-pack/plugins/apm/public/components/app/diagnostics/indices_tab.tsx @@ -27,7 +27,7 @@ export function DiagnosticsIndices() { return ; } - const { invalidIndices, validIndices } = diagnosticsBundle; + const { invalidIndices = [], validIndices = [] } = diagnosticsBundle; const columns: Array> = [ { field: 'index', diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/indicies_status.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/indicies_status.tsx index 419c7797595aa..4bd3853e25eac 100644 --- a/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/indicies_status.tsx +++ b/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/indicies_status.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { EuiLink } from '@elastic/eui'; +import { isEmpty } from 'lodash'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; @@ -45,5 +46,5 @@ export function getIsIndicesTabOk(diagnosticsBundle?: DiagnosticsBundle) { return true; } - return diagnosticsBundle.invalidIndices.length === 0; + return isEmpty(diagnosticsBundle.invalidIndices); } diff --git a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts index 3c993e83a5448..58e6d7293737e 100644 --- a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts +++ b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts @@ -110,6 +110,8 @@ async function handleInvalidIndexTemplateException(promise: Promise) { return []; } - throw error; + console.error(`Suppressed unknown exception: ${error.message}`); + + return []; } } diff --git a/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts b/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts index a3b28aeba9dd8..f00af8454fbe8 100644 --- a/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts +++ b/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts @@ -6,6 +6,7 @@ */ import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { NOT_AVAILABLE_LABEL } from '../../../common/i18n'; import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; import { getDataStreams } from './bundle/get_data_streams'; import { getNonDataStreamIndices } from './bundle/get_non_data_stream_indices'; @@ -15,7 +16,7 @@ import { getExistingApmIndexTemplates } from './bundle/get_existing_index_templa import { getIndicesStates } from './bundle/get_indices_states'; import { getApmEvents } from './bundle/get_apm_events'; import { getApmIndexTemplates } from './helpers/get_apm_index_template_names'; -import { handle403Exception } from './helpers/handle_403_exception'; +import { handleExceptions } from './helpers/handle_exceptions'; import { getDiagnosticsPrivileges } from './helpers/get_diagnostic_privileges'; const DEFEAULT_START = Date.now() - 60 * 5 * 1000; // 5 minutes @@ -39,62 +40,54 @@ export async function getDiagnosticsBundle({ apmIndices, }); - const indexTemplatesByIndexPattern = await handle403Exception( - getIndexTemplatesByIndexPattern({ - esClient, - apmIndices, - }), - [] - ); + const indexTemplatesByIndexPattern = + (await handleExceptions( + getIndexTemplatesByIndexPattern({ + esClient, + apmIndices, + }) + )) ?? []; - const existingIndexTemplates = await handle403Exception( - getExistingApmIndexTemplates({ - esClient, - }), - [] - ); + const existingIndexTemplates = + (await handleExceptions( + getExistingApmIndexTemplates({ + esClient, + }) + )) ?? []; - const dataStreams = await handle403Exception( - getDataStreams({ esClient, apmIndices }), - [] - ); - const nonDataStreamIndices = await handle403Exception( - getNonDataStreamIndices({ - esClient, - apmIndices, - }), - [] - ); + const dataStreams = + (await handleExceptions(getDataStreams({ esClient, apmIndices }))) ?? []; + + const nonDataStreamIndices = + (await handleExceptions( + getNonDataStreamIndices({ + esClient, + apmIndices, + }) + )) ?? []; const { invalidIndices, validIndices, indices, ingestPipelines, fieldCaps } = - await handle403Exception( + (await handleExceptions( getIndicesStates({ esClient, apmIndices, - }), - { - invalidIndices: [], - validIndices: [], - indices: [], - ingestPipelines: [], - fieldCaps: {}, - } - ); + }) + )) ?? {}; + + const apmEvents = + (await handleExceptions( + getApmEvents({ + esClient, + apmIndices, + start, + end, + kuery, + }) + )) ?? []; - const apmEvents = await handle403Exception( - getApmEvents({ - esClient, - apmIndices, - start, - end, - kuery, - }), - [] - ); - const elasticsearchVersion = await handle403Exception( - getElasticsearchVersion(esClient), - 'N/A' - ); + const elasticsearchVersion = + (await handleExceptions(getElasticsearchVersion(esClient))) ?? + NOT_AVAILABLE_LABEL; return { created_at: new Date().toISOString(), diff --git a/x-pack/plugins/apm/server/routes/diagnostics/helpers/handle_403_exception.ts b/x-pack/plugins/apm/server/routes/diagnostics/helpers/handle_exceptions.ts similarity index 81% rename from x-pack/plugins/apm/server/routes/diagnostics/helpers/handle_403_exception.ts rename to x-pack/plugins/apm/server/routes/diagnostics/helpers/handle_exceptions.ts index f201137643716..8b5fb902f21f6 100644 --- a/x-pack/plugins/apm/server/routes/diagnostics/helpers/handle_403_exception.ts +++ b/x-pack/plugins/apm/server/routes/diagnostics/helpers/handle_exceptions.ts @@ -6,10 +6,7 @@ */ import { errors } from '@elastic/elasticsearch'; -export async function handle403Exception( - promise: Promise, - defaultValue: unknown -) { +export async function handleExceptions(promise: Promise) { try { return await promise; } catch (error) { @@ -18,13 +15,13 @@ export async function handle403Exception( error.meta.statusCode === 403 ) { console.error(`Suppressed insufficient access error: ${error.message}}`); - return defaultValue as T; + return; } console.error( `Unhandled error: ${error.message} ${JSON.stringify(error)}}` ); - throw error; + return; } } diff --git a/x-pack/plugins/apm/server/routes/diagnostics/route.ts b/x-pack/plugins/apm/server/routes/diagnostics/route.ts index 3cde05ada9111..390e7bfa51f36 100644 --- a/x-pack/plugins/apm/server/routes/diagnostics/route.ts +++ b/x-pack/plugins/apm/server/routes/diagnostics/route.ts @@ -53,9 +53,9 @@ const getDiagnosticsRoute = createApmServerRoute({ ): Promise<{ esResponses: { existingIndexTemplates: IndicesGetIndexTemplateIndexTemplateItem[]; - fieldCaps: FieldCapsResponse; - indices: IndicesGetResponse; - ingestPipelines: IngestGetPipelineResponse; + fieldCaps?: FieldCapsResponse; + indices?: IndicesGetResponse; + ingestPipelines?: IngestGetPipelineResponse; }; diagnosticsPrivileges: { index: Record; @@ -77,8 +77,8 @@ const getDiagnosticsRoute = createApmServerRoute({ kibanaVersion: string; elasticsearchVersion: string; apmEvents: ApmEvent[]; - invalidIndices: IndiciesItem[]; - validIndices: IndiciesItem[]; + invalidIndices?: IndiciesItem[]; + validIndices?: IndiciesItem[]; dataStreams: IndicesDataStream[]; nonDataStreamIndices: string[]; indexTemplatesByIndexPattern: Array<{ From 19b3d50c712344419b80b33c625b0a0b3d41f8c3 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 21 Aug 2023 14:15:12 -0400 Subject: [PATCH 07/29] Removing refresh interval from session index (#164328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Specifying a `refresh_interval` below 5s is no longer allowed with es serverless. This PR removes the explicit `refresh_interval` from the session index. Work done in https://github.com/elastic/kibana/pull/151800 makes specifying a `refresh_interval` unnecessary. ## Flaky Test Runner [Session Tests x50 ea](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2932) 🟢 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/security/server/session_management/session_index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index cc3307d37adf1..0bb2fdac6cbd3 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -97,7 +97,6 @@ export function getSessionIndexSettings({ number_of_replicas: 0, auto_expand_replicas: '0-1', priority: 1000, - refresh_interval: '1s', hidden: true, }, aliases: { From b6fcd7900b973d0ee8ab8d9e5bfd5b38978a87f6 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 21 Aug 2023 20:36:03 +0200 Subject: [PATCH 08/29] [Security Solution][Detections] Fix "burning" test `detection_response/rule_creation/custom_query_rule.cy.ts` (#164312) ## Summary This PR fixes "burning" test `x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts` (`'Allows a rule to be edited'`). Failing job https://buildkite.com/elastic/kibana-pull-request/builds/151789#018a1792-bf45-4a4e-bf54-8372468d4cfd More details in slack https://elastic.slack.com/archives/C056TQ5J81Y/p1692621948405779 --- .../rule_creation/custom_query_rule.cy.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts index 5658a0d4aee3e..8c053a073fd66 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation/custom_query_rule.cy.ts @@ -352,14 +352,11 @@ describe('Custom query rules', { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, () const expectedEditedtags = rule.tags?.join(''); const expectedEditedIndexPatterns = rule.index; - before(() => { + beforeEach(() => { + login(); deleteAlertsAndRules(); deleteConnectors(); createRule(getExistingRule({ rule_id: 'rule1', enabled: true })); - }); - - beforeEach(() => { - login(); visit(DETECTIONS_RULE_MANAGEMENT_URL); }); From 03ee66ca72bd8efd50be696ad8b8b982923651d1 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:36:26 -0700 Subject: [PATCH 09/29] [ResponseOps][Alerting] Unskips alerting serverless tests (#164091) Related to https://github.com/elastic/response-ops-team/issues/124 ## Summary Fixes tests that had an interval less than 1m --- .../alerting/helpers/alerting_api_helper.ts | 6 +-- .../helpers/alerting_wait_for_helpers.ts | 50 +++++++++++++++-- .../test_suites/common/alerting/rules.ts | 53 +++++-------------- 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts index a8bcd98d89689..1ba17b77aca10 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts @@ -110,7 +110,7 @@ export async function createEsQueryRule({ name, rule_type_id: ruleTypeId, actions, - ...(notifyWhen ? { notify_when: notifyWhen, throttle: '1m' } : {}), + ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), }); return body; } @@ -173,11 +173,11 @@ export async function runRule({ supertest: SuperTest; ruleId: string; }) { - const { body } = await supertest + const response = await supertest .post(`/internal/alerting/rule/${ruleId}/_run_soon`) .set('kbn-xsrf', 'foo') .set('x-elastic-internal-origin', 'foo'); - return body; + return response; } export async function muteRule({ diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts index 1b5723cc07de5..eaa5e7b8ee61f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts @@ -6,11 +6,13 @@ */ import pRetry from 'p-retry'; +import type { SuperTest, Test } from 'supertest'; import type { Client } from '@elastic/elasticsearch'; import type { AggregationsAggregate, SearchResponse, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { runRule } from './alerting_api_helper'; export async function waitForDocumentInIndex({ esClient, @@ -250,15 +252,15 @@ export async function waitForDisabled({ ); } -export async function waitForEventLog({ +export async function waitForExecutionEventLog({ esClient, - provider, filter, + ruleId, num = 1, }: { esClient: Client; - provider: string; filter: Date; + ruleId: string; num?: number; }): Promise { return pRetry( @@ -269,10 +271,17 @@ export async function waitForEventLog({ query: { bool: { filter: [ + { + term: { + 'rule.id': { + value: ruleId, + }, + }, + }, { term: { 'event.provider': { - value: provider, + value: 'alerting', }, }, }, @@ -301,3 +310,36 @@ export async function waitForEventLog({ { retries: 10 } ); } + +export async function waitForNumRuleRuns({ + supertest, + numOfRuns, + ruleId, + esClient, + testStart, +}: { + supertest: SuperTest; + numOfRuns: number; + ruleId: string; + esClient: Client; + testStart: Date; +}) { + for (let i = 0; i < numOfRuns; i++) { + await pRetry( + async () => { + const resp = await runRule({ supertest, ruleId }); + if (resp.status !== 204) { + throw new Error(`Expected ${resp.status} to equal 204`); + } + await waitForExecutionEventLog({ + esClient, + filter: testStart, + ruleId, + num: i + 1, + }); + await waitForAllTasksIdle({ esClient, filter: testStart }); + }, + { retries: 10 } + ); + } +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts index c263a6f540c3e..bfce78384f601 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts @@ -26,7 +26,8 @@ import { waitForAllTasksIdle, waitForDisabled, waitForDocumentInIndex, - waitForEventLog, + waitForExecutionEventLog, + waitForNumRuleRuns, } from './helpers/alerting_wait_for_helpers'; export default function ({ getService }: FtrProviderContext) { @@ -34,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) { const esClient = getService('es'); const esDeleteAllIndices = getService('esDeleteAllIndices'); - describe.skip('Alerting rules', () => { + describe('Alerting rules', () => { const RULE_TYPE_ID = '.es-query'; const ALERT_ACTION_INDEX = 'alert-action-es-query'; let actionId: string; @@ -148,10 +149,10 @@ export default function ({ getService }: FtrProviderContext) { tags: '', }); - const eventLogResp = await waitForEventLog({ + const eventLogResp = await waitForExecutionEventLog({ esClient, - provider: 'alerting', filter: testStart, + ruleId, }); expect(eventLogResp.hits.hits.length).to.be(1); @@ -351,7 +352,7 @@ export default function ({ getService }: FtrProviderContext) { consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, - schedule: { interval: '5s' }, + schedule: { interval: '1m' }, notifyWhen: 'onThrottleInterval', params: { size: 100, @@ -389,13 +390,7 @@ export default function ({ getService }: FtrProviderContext) { expect(ruleId).not.to.be(undefined); // Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish - const eventLogResp = await waitForEventLog({ - esClient, - provider: 'alerting', - filter: testStart, - num: 3, - }); - expect(eventLogResp.hits.hits.length >= 3).to.be(true); + await waitForNumRuleRuns({ supertest, numOfRuns: 3, ruleId, esClient, testStart }); await disableRule({ supertest, @@ -431,7 +426,7 @@ export default function ({ getService }: FtrProviderContext) { consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, - schedule: { interval: '5s' }, + schedule: { interval: '1m' }, params: { size: 100, thresholdComparator: '>', @@ -463,7 +458,7 @@ export default function ({ getService }: FtrProviderContext) { }, frequency: { notify_when: 'onThrottleInterval', - throttle: '1m', + throttle: '5m', summary: false, }, }, @@ -473,13 +468,7 @@ export default function ({ getService }: FtrProviderContext) { expect(ruleId).not.to.be(undefined); // Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish - const eventLogResp = await waitForEventLog({ - esClient, - provider: 'alerting', - filter: testStart, - num: 3, - }); - expect(eventLogResp.hits.hits.length >= 3).to.be(true); + await waitForNumRuleRuns({ supertest, numOfRuns: 3, ruleId, esClient, testStart }); await disableRule({ supertest, @@ -616,10 +605,10 @@ export default function ({ getService }: FtrProviderContext) { ruleId, }); - const eventLogResp = await waitForEventLog({ + const eventLogResp = await waitForExecutionEventLog({ esClient, - provider: 'alerting', filter: testStart, + ruleId, num: 2, }); expect(eventLogResp.hits.hits.length).to.be(2); @@ -661,7 +650,6 @@ export default function ({ getService }: FtrProviderContext) { consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, - schedule: { interval: '5s' }, params: { size: 100, thresholdComparator: '>', @@ -714,13 +702,7 @@ export default function ({ getService }: FtrProviderContext) { // Wait until alerts schedule actions twice to ensure actions had a chance to skip // execution once before disabling the alert and waiting for tasks to finish - const eventLogResp = await waitForEventLog({ - esClient, - provider: 'alerting', - filter: testStart, - num: 2, - }); - expect(eventLogResp.hits.hits.length >= 2).to.be(true); + await waitForNumRuleRuns({ supertest, numOfRuns: 2, ruleId, esClient, testStart }); await disableRule({ supertest, @@ -758,7 +740,6 @@ export default function ({ getService }: FtrProviderContext) { consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, - schedule: { interval: '5s' }, params: { size: 100, thresholdComparator: '>', @@ -812,13 +793,7 @@ export default function ({ getService }: FtrProviderContext) { // Wait until alerts schedule actions twice to ensure actions had a chance to skip // execution once before disabling the alert and waiting for tasks to finish - const eventLogResp = await waitForEventLog({ - esClient, - provider: 'alerting', - filter: testStart, - num: 2, - }); - expect(eventLogResp.hits.hits.length >= 2).to.be(true); + await waitForNumRuleRuns({ supertest, numOfRuns: 2, ruleId, esClient, testStart }); await disableRule({ supertest, From 49f95280518d9c3c75a6efd1f2ca488fac18eaf4 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 21 Aug 2023 20:36:44 +0200 Subject: [PATCH 10/29] =?UTF-8?q?Failing=20test:=20Security=20Solution=20C?= =?UTF-8?q?ypress.x-pack/test/security=5Fsolution=5Fcypress/cypress/e2e/de?= =?UTF-8?q?tection=5Fresponse/rule=5Fcreation/custom=5Fquery=5Frule=C2=B7c?= =?UTF-8?q?y=C2=B7ts=20-=20Custom=20query=20rules=20Custom=20detection=20r?= =?UTF-8?q?ules=20deletion=20and=20edition=20Deletion=20Deletes=20one=20ru?= =?UTF-8?q?le=20from=20detail=20page=20Deletes=20one=20rule=20from=20detai?= =?UTF-8?q?l=20page=20#163977=20(#164327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes: https://github.com/elastic/kibana/issues/163977 Fixes: https://github.com/elastic/kibana/issues/163568 These changes fix the issue with actions popover and the way we check whether it is closed on rule's detail page. The issue happens due to the fact that after we closed the popover we should be testing `should('not.exist')` instead of `should('not.be.visible')`. --- .../cypress/tasks/alerts_detection_rules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts index 221c3d6a61501..6604cdfe1ba90 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts_detection_rules.ts @@ -129,7 +129,7 @@ export const deleteRuleFromDetailsPage = () => { cy.wait(1000); cy.get(ALL_ACTIONS).click(); cy.get(RULE_DETAILS_DELETE_BTN).click(); - cy.get(RULE_DETAILS_DELETE_BTN).should('not.be.visible'); + cy.get(RULE_DETAILS_DELETE_BTN).should('not.exist'); cy.get(CONFIRM_DELETE_RULE_BTN).click(); }; From 4477f642e3297355ef676dcf485efb0cb49c4fcb Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 21 Aug 2023 20:40:14 +0200 Subject: [PATCH 11/29] [Security Solution] [Detections] Fixes flakey exceptions read-only viewer cypress test (#164283) ## Summary Fixes: https://github.com/elastic/kibana/issues/162569 Fixes: https://github.com/elastic/kibana/issues/164061 Fixes: https://github.com/elastic/kibana/issues/164058 Fixes: https://github.com/elastic/kibana/issues/163546 Fixes: https://github.com/elastic/kibana/issues/162669 We tried to fix the issue with this PR https://github.com/elastic/kibana/pull/162839 but test failed again. This is another attempt to fix it using the @jpdjere's approach where we disable rule's table refreshing (https://github.com/elastic/kibana/pull/163698). --- .../exceptions/rule_details_flow/read_only_view.cy.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts index 0e0aaeea06ddc..626bb6ff80bcd 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/rule_details_flow/read_only_view.cy.ts @@ -12,7 +12,11 @@ import { getNewRule } from '../../../objects/rule'; import { createRule } from '../../../tasks/api_calls/rules'; import { login, visitWithoutDateRange } from '../../../tasks/login'; import { goToExceptionsTab, goToAlertsTab } from '../../../tasks/rule_details'; -import { goToRuleDetails } from '../../../tasks/alerts_detection_rules'; +import { + disableAutoRefresh, + goToRuleDetails, + waitForRulesTableToBeLoaded, +} from '../../../tasks/alerts_detection_rules'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; import { cleanKibana, deleteAlertsAndRules } from '../../../tasks/common'; import { @@ -56,8 +60,9 @@ describe('Exceptions viewer read only', { tags: tag.ESS }, () => { beforeEach(() => { login(ROLES.reader); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL, ROLES.reader); + waitForRulesTableToBeLoaded(); + disableAutoRefresh(); goToRuleDetails(); - cy.url().should('contain', 'app/security/rules/id'); goToExceptionsTab(); }); From dc3b4862ed4d70e2a7739f42d1d1ae0eafc06ac5 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Mon, 21 Aug 2023 14:44:31 -0400 Subject: [PATCH 12/29] feat(slo): persist auto refresh state in localstorage (#163615) --- .../auto_refresh_button.stories.tsx | 0 .../auto_refresh_button.tsx | 12 ++------ .../hooks/use_auto_refresh_storage.tsx | 29 +++++++++++++++++++ .../slo/auto_refresh_button/index.tsx | 8 +++++ .../public/pages/slo_details/slo_details.tsx | 7 +++-- .../observability/public/pages/slos/slos.tsx | 7 +++-- 6 files changed, 50 insertions(+), 13 deletions(-) rename x-pack/plugins/observability/public/{pages/slos/components => components/slo/auto_refresh_button}/auto_refresh_button.stories.tsx (100%) rename x-pack/plugins/observability/public/{pages/slos/components => components/slo/auto_refresh_button}/auto_refresh_button.tsx (82%) create mode 100644 x-pack/plugins/observability/public/components/slo/auto_refresh_button/hooks/use_auto_refresh_storage.tsx create mode 100644 x-pack/plugins/observability/public/components/slo/auto_refresh_button/index.tsx diff --git a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx b/x-pack/plugins/observability/public/components/slo/auto_refresh_button/auto_refresh_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.stories.tsx rename to x-pack/plugins/observability/public/components/slo/auto_refresh_button/auto_refresh_button.stories.tsx diff --git a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx b/x-pack/plugins/observability/public/components/slo/auto_refresh_button/auto_refresh_button.tsx similarity index 82% rename from x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx rename to x-pack/plugins/observability/public/components/slo/auto_refresh_button/auto_refresh_button.tsx index 5101a5c1d96ef..c19c3d19e54d9 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/auto_refresh_button.tsx +++ b/x-pack/plugins/observability/public/components/slo/auto_refresh_button/auto_refresh_button.tsx @@ -10,20 +10,14 @@ import { i18n } from '@kbn/i18n'; interface Props { isAutoRefreshing: boolean; - dataTestSubj?: string; disabled?: boolean; onClick: () => void; } -export function AutoRefreshButton({ - dataTestSubj = 'autoRefreshButton', - disabled, - isAutoRefreshing, - onClick, -}: Props) { +export function AutoRefreshButton({ disabled, isAutoRefreshing, onClick }: Props) { return isAutoRefreshing ? ( ) : ( void; + getAutoRefreshState: () => boolean; +} { + if (!localStorage) { + return { storeAutoRefreshState: () => {}, getAutoRefreshState: () => true }; + } + + return { + storeAutoRefreshState: (newValue: boolean) => { + localStorage.setItem(AUTO_REFRESH_STORAGE_KEY, JSON.stringify(newValue)); + }, + + getAutoRefreshState: () => { + const value = localStorage.getItem(AUTO_REFRESH_STORAGE_KEY); + if (value === null) return true; + + return Boolean(JSON.parse(value)); + }, + }; +} diff --git a/x-pack/plugins/observability/public/components/slo/auto_refresh_button/index.tsx b/x-pack/plugins/observability/public/components/slo/auto_refresh_button/index.tsx new file mode 100644 index 0000000000000..2f7dd5fe69d16 --- /dev/null +++ b/x-pack/plugins/observability/public/components/slo/auto_refresh_button/index.tsx @@ -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 * from './auto_refresh_button'; diff --git a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx index ac806e976e336..6b50fbde29268 100644 --- a/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx +++ b/x-pack/plugins/observability/public/pages/slo_details/slo_details.tsx @@ -25,9 +25,10 @@ import { HeaderTitle } from './components/header_title'; import { HeaderControl } from './components/header_control'; import { paths } from '../../../common/locators/paths'; import type { SloDetailsPathParams } from './types'; -import { AutoRefreshButton } from '../slos/components/auto_refresh_button'; +import { AutoRefreshButton } from '../../components/slo/auto_refresh_button'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; import { useGetInstanceIdQueryParam } from './hooks/use_get_instance_id_query_param'; +import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function SloDetailsPage() { @@ -42,7 +43,8 @@ export function SloDetailsPage() { const { sloId } = useParams(); const sloInstanceId = useGetInstanceIdQueryParam(); - const [isAutoRefreshing, setIsAutoRefreshing] = useState(true); + const { storeAutoRefreshState, getAutoRefreshState } = useAutoRefreshStorage(); + const [isAutoRefreshing, setIsAutoRefreshing] = useState(getAutoRefreshState()); const { isLoading, slo } = useFetchSloDetails({ sloId, instanceId: sloInstanceId, @@ -65,6 +67,7 @@ export function SloDetailsPage() { const handleToggleAutoRefresh = () => { setIsAutoRefreshing(!isAutoRefreshing); + storeAutoRefreshState(!isAutoRefreshing); }; return ( diff --git a/x-pack/plugins/observability/public/pages/slos/slos.tsx b/x-pack/plugins/observability/public/pages/slos/slos.tsx index 935aae8614a88..a183d7941f7e1 100644 --- a/x-pack/plugins/observability/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability/public/pages/slos/slos.tsx @@ -16,10 +16,11 @@ import { useLicense } from '../../hooks/use_license'; import { useCapabilities } from '../../hooks/slo/use_capabilities'; import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { SloList } from './components/slo_list'; -import { AutoRefreshButton } from './components/auto_refresh_button'; +import { AutoRefreshButton } from '../../components/slo/auto_refresh_button'; import { HeaderTitle } from './components/header_title'; import { FeedbackButton } from '../../components/slo/feedback_button/feedback_button'; import { paths } from '../../../common/locators/paths'; +import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; export function SlosPage() { @@ -34,7 +35,8 @@ export function SlosPage() { const { isInitialLoading, isLoading, isError, sloList } = useFetchSloList(); const { total } = sloList || { total: 0 }; - const [isAutoRefreshing, setIsAutoRefreshing] = useState(true); + const { storeAutoRefreshState, getAutoRefreshState } = useAutoRefreshStorage(); + const [isAutoRefreshing, setIsAutoRefreshing] = useState(getAutoRefreshState()); useBreadcrumbs([ { @@ -57,6 +59,7 @@ export function SlosPage() { const handleToggleAutoRefresh = () => { setIsAutoRefreshing(!isAutoRefreshing); + storeAutoRefreshState(!isAutoRefreshing); }; if (isInitialLoading) { From 0317acec7495b8b498a609157fe671cce8834b7f Mon Sep 17 00:00:00 2001 From: GitStart <1501599+gitstart@users.noreply.github.com> Date: Mon, 21 Aug 2023 19:50:11 +0100 Subject: [PATCH 13/29] [Snapshot Restore] Migrate all usages of EuiPage*_Deprecated (#163130) --- .../__jest__/client_integration/home.test.ts | 6 +- .../public/application/app.tsx | 41 ++-- .../public/application/components/loading.tsx | 14 +- .../sections/home/policy_list/policy_list.tsx | 81 +++----- .../home/repository_list/repository_list.tsx | 79 +++---- .../home/restore_list/restore_list.tsx | 70 +++---- .../components/repository_empty_prompt.tsx | 76 +++---- .../components/repository_error.tsx | 65 +++--- .../components/snapshot_empty_prompt.tsx | 195 ++++++++---------- .../sections/policy_add/policy_add.tsx | 10 +- .../sections/policy_edit/policy_edit.tsx | 11 +- .../repository_add/repository_add.tsx | 10 +- .../repository_edit/repository_edit.tsx | 11 +- .../restore_snapshot/restore_snapshot.tsx | 10 +- 14 files changed, 297 insertions(+), 382 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 715d385f0de26..3e38602b4be89 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -454,13 +454,13 @@ describe('', () => { test('should display an empty prompt', () => { const { exists } = testBed; - expect(exists('emptyPrompt')).toBe(true); + expect(exists('snapshotListEmpty')).toBe(true); }); test('should invite the user to first register a repository', () => { const { find, exists } = testBed; - expect(find('emptyPrompt.title').text()).toBe('Start by registering a repository'); - expect(exists('emptyPrompt.registerRepositoryButton')).toBe(true); + expect(find('snapshotListEmpty.title').text()).toBe('Start by registering a repository'); + expect(exists('snapshotListEmpty.registerRepositoryButton')).toBe(true); }); }); diff --git a/x-pack/plugins/snapshot_restore/public/application/app.tsx b/x-pack/plugins/snapshot_restore/public/application/app.tsx index 1a2be7de25170..5289c31dcce4c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { Redirect } from 'react-router-dom'; import { Routes, Route } from '@kbn/shared-ux-router'; -import { EuiPageContent_Deprecated as EuiPageContent } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; @@ -87,27 +86,25 @@ export const App: React.FunctionComponent = () => {
) : ( - - - } - message={ - - } - /> - + + } + message={ + + } + /> ) } diff --git a/x-pack/plugins/snapshot_restore/public/application/components/loading.tsx b/x-pack/plugins/snapshot_restore/public/application/components/loading.tsx index 1ab7523ce17ea..e57cf3ebf10f8 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/loading.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/loading.tsx @@ -14,7 +14,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTextColor, - EuiPageContent_Deprecated as EuiPageContent, + EuiPageTemplate, } from '@elastic/eui'; interface Props { @@ -55,12 +55,10 @@ export const SectionLoading: React.FunctionComponent = ({ children }) => */ export const PageLoading: React.FunctionComponent = ({ children }) => { return ( - - } - body={{children}} - data-test-subj="sectionLoading" - /> - + } + body={{children}} + data-test-subj="sectionLoading" + /> ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index 5519a68aa9d34..aa7a35bb2c0b2 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -8,13 +8,7 @@ import React, { Fragment, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContent_Deprecated as EuiPageContent, - EuiEmptyPrompt, - EuiButton, - EuiCallOut, - EuiSpacer, -} from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiSpacer, EuiPageTemplate } from '@elastic/eui'; import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; @@ -123,48 +117,41 @@ export const PolicyList: React.FunctionComponent - - - - } - body={ - -

- -

-
- } - actions={ - + + + + } + body={ + +

- - } - data-test-subj="emptyPrompt" - /> - +

+
+ } + actions={ + + + + } + data-test-subj="emptyPrompt" + /> ); } else { const policySchedules = policies.map((policy: SlmPolicy) => policy.schedule); diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx index f55264f5e6843..9da7fb80d8581 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_list.tsx @@ -8,11 +8,7 @@ import React, { Fragment, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContent_Deprecated as EuiPageContent, - EuiButton, - EuiEmptyPrompt, -} from '@elastic/eui'; +import { EuiButton, EuiPageTemplate } from '@elastic/eui'; import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; @@ -102,48 +98,41 @@ export const RepositoryList: React.FunctionComponent - - - - } - body={ - -

- -

-
- } - actions={ - + + + + } + body={ + +

- - } - data-test-subj="emptyPrompt" - /> - +

+
+ } + actions={ + + + + } + data-test-subj="emptyPrompt" + /> ); } else { content = ( diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx index 85f5ba4c33fef..8f824aab3b7f3 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/restore_list/restore_list.tsx @@ -8,8 +8,6 @@ import React, { useEffect, useState, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { - EuiPageContent_Deprecated as EuiPageContent, - EuiEmptyPrompt, EuiPopover, EuiButtonEmpty, EuiContextMenuPanel, @@ -19,6 +17,7 @@ import { EuiSpacer, EuiLoadingSpinner, EuiLink, + EuiPageTemplate, } from '@elastic/eui'; import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; import { APP_RESTORE_INDEX_PRIVILEGES } from '../../../../../common'; @@ -106,45 +105,38 @@ export const RestoreList: React.FunctionComponent = () => { } else { if (restores && restores.length === 0) { content = ( - - + + + + } + body={ + +

+ + + ), + }} /> - - } - body={ - -

- - - - ), - }} - /> -

-
- } - data-test-subj="emptyPrompt" - /> -
+

+ + } + data-test-subj="emptyPrompt" + /> ); } else { content = ( diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx index 43970cbf09145..c3d36d53c16f5 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_empty_prompt.tsx @@ -7,11 +7,7 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; -import { - EuiButton, - EuiEmptyPrompt, - EuiPageContent_Deprecated as EuiPageContent, -} from '@elastic/eui'; +import { EuiButton, EuiPageTemplate } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { reactRouterNavigate } from '../../../../../shared_imports'; import { linkToAddRepository } from '../../../../services/navigation'; @@ -19,48 +15,40 @@ import { linkToAddRepository } from '../../../../services/navigation'; export const RepositoryEmptyPrompt: React.FunctionComponent = () => { const history = useHistory(); return ( - - + + + + } + body={ + <> +

- - } - body={ - <> -

+

+

+ -

-

- - - -

- - } - data-test-subj="emptyPrompt" - /> -
+
+

+ + } + data-test-subj="snapshotListEmpty" + /> ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx index 29fcab6d2a5b5..08d2b9589e803 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/repository_error.tsx @@ -8,44 +8,43 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiEmptyPrompt, EuiLink, EuiPageContent_Deprecated as EuiPageContent } from '@elastic/eui'; +import { EuiLink, EuiPageTemplate } from '@elastic/eui'; import { reactRouterNavigate } from '../../../../../shared_imports'; import { linkToRepositories } from '../../../../services/navigation'; export const RepositoryError: React.FunctionComponent = () => { const history = useHistory(); return ( - - - - - } - body={ -

- - - - ), - }} - /> -

- } - /> -
+ + + + } + body={ +

+ + + + ), + }} + /> +

+ } + /> ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx index 13cca3329fc99..ae3a59252f94f 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_empty_prompt.tsx @@ -7,13 +7,7 @@ import React, { Fragment } from 'react'; import { useHistory } from 'react-router-dom'; -import { - EuiButton, - EuiEmptyPrompt, - EuiIcon, - EuiLink, - EuiPageContent_Deprecated as EuiPageContent, -} from '@elastic/eui'; +import { EuiButton, EuiIcon, EuiLink, EuiPageTemplate } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../../common'; import { reactRouterNavigate, WithPrivileges } from '../../../../../shared_imports'; @@ -26,104 +20,97 @@ export const SnapshotEmptyPrompt: React.FunctionComponent<{ policiesCount: numbe const { docLinks } = useCore(); const history = useHistory(); return ( - - - - - } - body={ - `cluster.${name}`)}> - {({ hasPrivileges }) => - hasPrivileges ? ( - -

- - - - ), - }} - /> -

-

- {policiesCount === 0 ? ( - - - - ) : ( - - - - )} -

-
- ) : ( - -

- -

-

- + + + } + body={ + `cluster.${name}`)}> + {({ hasPrivileges }) => + hasPrivileges ? ( + +

+ + + + ), + }} + /> +

+

+ {policiesCount === 0 ? ( + {' '} - - -

-
- ) - } -
- } - data-test-subj="emptyPrompt" - /> -
+ id="xpack.snapshotRestore.snapshotList.emptyPrompt.addPolicyText" + defaultMessage="Create a policy" + /> +
+ ) : ( + + + + )} +

+ + ) : ( + +

+ +

+

+ + {' '} + + +

+
+ ) + } + + } + data-test-subj="emptyPrompt" + /> ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx index 74182b37e38e8..4d92e27ca8665 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_add/policy_add.tsx @@ -9,11 +9,7 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiSpacer, - EuiPageHeader, -} from '@elastic/eui'; +import { EuiPageSection, EuiSpacer, EuiPageHeader } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../common/types'; import { TIME_UNITS } from '../../../../common'; @@ -117,7 +113,7 @@ export const PolicyAdd: React.FunctionComponent = ({ } return ( - + @@ -142,6 +138,6 @@ export const PolicyAdd: React.FunctionComponent = ({ onSave={onSave} onCancel={onCancel} /> - + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx index 652b2f78ff4dc..0246c91d12e5b 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/policy_edit/policy_edit.tsx @@ -9,12 +9,7 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiPageHeader, - EuiSpacer, - EuiCallOut, -} from '@elastic/eui'; +import { EuiPageSection, EuiPageHeader, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { SlmPolicyPayload } from '../../../../common/types'; import { SectionError, Error, PageError } from '../../../shared_imports'; import { useDecodedParams } from '../../lib'; @@ -200,7 +195,7 @@ export const PolicyEdit: React.FunctionComponent + @@ -242,6 +237,6 @@ export const PolicyEdit: React.FunctionComponent - + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx index 108f90ef3f206..5b5da02b9324d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_add/repository_add.tsx @@ -10,11 +10,7 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiSpacer, - EuiPageHeader, -} from '@elastic/eui'; +import { EuiPageSection, EuiSpacer, EuiPageHeader } from '@elastic/eui'; import { Repository, EmptyRepository } from '../../../../common/types'; import { SectionError } from '../../../shared_imports'; @@ -83,7 +79,7 @@ export const RepositoryAdd: React.FunctionComponent = ({ }; return ( - + @@ -104,6 +100,6 @@ export const RepositoryAdd: React.FunctionComponent = ({ clearSaveError={clearSaveError} onSave={onSave} /> - + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx index 94e1afb036fac..f10343be41a0e 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/repository_edit/repository_edit.tsx @@ -9,12 +9,7 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiCallOut, - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiPageHeader, - EuiSpacer, -} from '@elastic/eui'; +import { EuiCallOut, EuiPageSection, EuiPageHeader, EuiSpacer } from '@elastic/eui'; import { Repository, EmptyRepository } from '../../../../common/types'; import { PageError, SectionError, Error } from '../../../shared_imports'; @@ -152,7 +147,7 @@ export const RepositoryEdit: React.FunctionComponent + @@ -192,6 +187,6 @@ export const RepositoryEdit: React.FunctionComponent - + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx index b600f6d9e7264..81b6ce02c42cc 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/restore_snapshot/restore_snapshot.tsx @@ -8,11 +8,7 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContentBody_Deprecated as EuiPageContentBody, - EuiPageHeader, - EuiSpacer, -} from '@elastic/eui'; +import { EuiPageSection, EuiPageHeader, EuiSpacer } from '@elastic/eui'; import { SnapshotDetails, RestoreSettings } from '../../../../common/types'; import { SectionError, Error, PageError } from '../../../shared_imports'; @@ -149,7 +145,7 @@ export const RestoreSnapshot: React.FunctionComponent + @@ -171,6 +167,6 @@ export const RestoreSnapshot: React.FunctionComponent - + ); }; From 9c17de6bdb1bb3b750b590fa1a24f00c9e68cbae Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Mon, 21 Aug 2023 19:54:00 +0100 Subject: [PATCH 14/29] Add indexName to data quality telemetry (#163937) ## Summary [Staging](https://telemetry-v2-staging.elastic.dev/s/securitysolution/app/r/s/1rf1f) https://github.com/elastic/kibana/assets/6295984/50d4fbbd-2ce4-4fb7-be73-e33bb242a261 ``` { "timestamp": "2023-08-15T13:39:27.513Z", "event_type": "Data Quality Index Checked", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "5d0d6127-4b9b-47af-8bb1-96e90fcdbb31", "ecsVersion": "8.6.1", "errorCount": 0, "ilmPhase": "hot", "indexId": "WxZsLLXbR9qeroNaFeY1wg", "indexName": ".internal.alerts-security.alerts-default-000001", "isCheckAll": true, "numberOfDocuments": 7834, "numberOfIncompatibleFields": 0, "numberOfIndices": 1, "numberOfIndicesChecked": 1, "sizeInBytes": 15097503, "timeConsumedMs": 151, "unallowedMappingFields": [], "unallowedValueFields": [] } } { "timestamp": "2023-08-15T13:39:30.637Z", "event_type": "Data Quality Index Checked", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "5d0d6127-4b9b-47af-8bb1-96e90fcdbb31", "ecsVersion": "8.6.1", "errorCount": 0, "ilmPhase": "unmanaged", "indexId": "AoyctcRqTKG8HvUVmpuzEA", "indexName": "auditbeat-custom-index-1", "isCheckAll": true, "numberOfDocuments": 4, "numberOfIncompatibleFields": 3, "numberOfIndices": 1, "numberOfIndicesChecked": 1, "sizeInBytes": 28417, "timeConsumedMs": 69, "unallowedMappingFields": [ "host.name", "source.ip" ], "unallowedValueFields": [ "event.category" ] } } { "timestamp": "2023-08-15T13:39:33.806Z", "event_type": "Data Quality Index Checked", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "5d0d6127-4b9b-47af-8bb1-96e90fcdbb31", "ecsVersion": "8.6.1", "errorCount": 0, "ilmPhase": "hot", "indexId": "56NqQP_eSNCnesjLPmoe1g", "indexName": ".ds-auditbeat-8.7.1-2023.08.14-000001", "isCheckAll": true, "numberOfDocuments": 13593, "numberOfIncompatibleFields": 0, "numberOfIndices": 1, "numberOfIndicesChecked": 1, "sizeInBytes": 10588378, "timeConsumedMs": 134, "unallowedMappingFields": [], "unallowedValueFields": [] } } { "timestamp": "2023-08-15T13:39:37.013Z", "event_type": "Data Quality Index Checked", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "5d0d6127-4b9b-47af-8bb1-96e90fcdbb31", "ecsVersion": "8.6.1", "errorCount": 0, "ilmPhase": "hot", "indexId": "hjnJ8WyPR5uTLw3fBISMmA", "indexName": ".ds-packetbeat-8.8.0-2023.08.14-000001", "isCheckAll": true, "numberOfDocuments": 318749, "numberOfIncompatibleFields": 0, "numberOfIndices": 1, "numberOfIndicesChecked": 1, "sizeInBytes": 165604512, "timeConsumedMs": 157, "unallowedMappingFields": [], "unallowedValueFields": [] } } { "timestamp": "2023-08-15T13:39:37.013Z", "event_type": "Data Quality Check All Completed", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "5d0d6127-4b9b-47af-8bb1-96e90fcdbb31", "ecsVersion": "8.6.1", "isCheckAll": true, "numberOfDocuments": 340180, "numberOfIncompatibleFields": 3, "numberOfIndices": 4, "numberOfIndicesChecked": 4, "sizeInBytes": 191318810, "timeConsumedMs": 9651 } } ``` https://github.com/elastic/kibana/assets/6295984/5c977f60-e78d-426e-a682-46f7b1de4138 ``` { "timestamp": "2023-08-15T13:42:47.777Z", "event_type": "Data Quality Index Checked", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "cb8c7d1d-d84c-41a3-8e9b-cb3991817df0", "ecsVersion": "8.6.1", "errorCount": 0, "ilmPhase": "hot", "indexId": "WxZsLLXbR9qeroNaFeY1wg", "indexName": ".internal.alerts-security.alerts-default-000001", "isCheckAll": false, "numberOfDocuments": 7834, "numberOfIncompatibleFields": 0, "numberOfIndices": 1, "numberOfIndicesChecked": 1, "sizeInBytes": 15097503, "timeConsumedMs": 121, "unallowedMappingFields": [], "unallowedValueFields": [] } } { "timestamp": "2023-08-15T13:43:00.076Z", "event_type": "Data Quality Index Checked", "context": { "isDev": true, "isDistributable": false, "version": "8.10.0", "branch": "main", "buildNum": 9007199254740991, "buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "session_id": "99ed0d92-71f7-4e48-bdbb-a03bb8ac31e3", "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36", "preferred_language": "en-US", "preferred_languages": [ "en-US", "en" ], "viewport_width": 2560, "viewport_height": 934, "cluster_name": "elasticsearch", "cluster_uuid": "efIxsMivQne1nV2Y44MW5A", "cluster_version": "8.10.0-SNAPSHOT", "pageName": "application:securitySolutionUI:/data_quality", "applicationId": "securitySolutionUI", "page": "/data_quality", "entityId": "new", "page_title": "Elastic", "page_url": "/app/security/data_quality#?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:now-15m,to:now))&_a=(columns:!(),filters:!(),index:security-solution-default,interval:auto,query:(language:kuery,query:''),sort:!(!('@timestamp',desc)))", "license_id": "d8d531da-1994-4e24-a4cc-13d7ea30c339", "license_status": "active", "license_type": "trial", "labels": { "serverless": "security" }, "userId": "986051385feae5b9850804db2d701c0b029ad24f09bce340c12aee7a5c8a0391", "isElasticCloudUser": false }, "properties": { "batchId": "ca4e6e41-0025-47c6-ab46-e4a7b6911b4f", "ecsVersion": "8.6.1", "errorCount": 0, "ilmPhase": "unmanaged", "indexId": "AoyctcRqTKG8HvUVmpuzEA", "indexName": "auditbeat-custom-index-1", "isCheckAll": false, "numberOfDocuments": 4, "numberOfIncompatibleFields": 3, "numberOfIndices": 1, "numberOfIndicesChecked": 1, "sizeInBytes": 28417, "timeConsumedMs": 91, "unallowedMappingFields": [ "host.name", "source.ip" ], "unallowedValueFields": [ "event.category" ] } } ``` --- .../data_quality_panel/index_properties/index.tsx | 1 + .../ecs_data_quality_dashboard/impl/data_quality/types.ts | 1 + .../impl/data_quality/use_results_rollup/index.tsx | 1 + .../common/lib/telemetry/events/data_quality/index.ts | 7 +++++++ .../common/lib/telemetry/events/data_quality/types.ts | 3 ++- 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx index 4ff6edd007a27..1fc0789af6e53 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx @@ -265,6 +265,7 @@ const IndexPropertiesComponent: React.FC = ({ errorCount: error ? 1 : 0, ilmPhase, indexId, + indexName, isCheckAll: false, numberOfDocuments: docsCount, numberOfIncompatibleFields: indexIncompatible, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts index 02a9fba7f3fbf..7f99c0cb8f1e7 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts @@ -187,6 +187,7 @@ export type DataQualityIndexCheckedParams = DataQualityCheckAllCompletedParams & errorCount?: number; ilmPhase?: string; indexId: string; + indexName: string; unallowedMappingFields?: string[]; unallowedValueFields?: string[]; }; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx index 44eb238da6135..652c07ce29275 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_results_rollup/index.tsx @@ -121,6 +121,7 @@ export const useResultsRollup = ({ ilmPhases, patterns }: Props): UseResultsRoll errorCount: error ? 1 : 0, ilmPhase: getIlmPhase(ilmExplain[indexName]), indexId, + indexName, isCheckAll: true, numberOfDocuments: getDocsCount({ indexName, stats: updated[pattern].stats }), numberOfIncompatibleFields: getIndexIncompatible({ diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts index cdd0643e3dc11..042daf5ab88de 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/index.ts @@ -28,6 +28,13 @@ export const dataQualityIndexCheckedEvent: DataQualityTelemetryIndexCheckedEvent optional: false, }, }, + indexName: { + type: 'keyword', + _meta: { + description: 'Index name', + optional: false, + }, + }, numberOfIndices: { type: 'integer', _meta: { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts index 8c7b01e15f65e..aab166fc6a8af 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts @@ -10,8 +10,9 @@ import type { TelemetryEventTypes } from '../../constants'; export type ReportDataQualityIndexCheckedParams = ReportDataQualityCheckAllCompletedParams & { errorCount?: number; - indexId: string; ilmPhase?: string; + indexId: string; + indexName: string; unallowedMappingFields?: string[]; unallowedValueFields?: string[]; }; From b5af8c880ee73dedb3e823bb1a8e387f3aae3c5d Mon Sep 17 00:00:00 2001 From: GitStart <1501599+gitstart@users.noreply.github.com> Date: Mon, 21 Aug 2023 19:54:55 +0100 Subject: [PATCH 15/29] [Upgrade Assistant] Migrate all usages of EuiPage*_Deprecated (#163127) --- .../components/not_authorized_section.tsx | 10 +- .../public/application/app.tsx | 147 +++++++----------- .../es_deprecation_logs.tsx | 6 +- .../es_deprecations/es_deprecations.tsx | 24 +-- .../kibana_deprecations.tsx | 23 +-- .../components/overview/overview.tsx | 6 +- .../deprecations_page_loading_error.tsx | 31 ++-- .../components/shared/no_deprecations.tsx | 4 +- 8 files changed, 97 insertions(+), 154 deletions(-) diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx index da3454b5faa3f..f896826eca6d5 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/not_authorized_section.tsx @@ -12,8 +12,14 @@ import { EuiEmptyPrompt } from '@elastic/eui'; interface Props { title: React.ReactNode; message: React.ReactNode | string; + dataTestSubj?: string; } -export const NotAuthorizedSection = ({ title, message }: Props) => ( - {title}} body={

{message}

} /> +export const NotAuthorizedSection = ({ title, message, dataTestSubj }: Props) => ( + {title}} + body={

{message}

} + /> ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx index a61468e2e5ebe..90de3755c0d76 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -11,11 +11,7 @@ import { Redirect } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiEmptyPrompt, - EuiPageContent_Deprecated as EuiPageContent, - EuiLoadingSpinner, -} from '@elastic/eui'; +import { EuiLoadingSpinner, EuiPageTemplate } from '@elastic/eui'; import { ScopedHistory } from '@kbn/core/public'; import { API_BASE_PATH } from '../../common/constants'; @@ -52,94 +48,72 @@ const AppHandlingClusterUpgradeState: React.FunctionComponent = () => { if (missingManageSpacesPrivilege) { return ( - - - } - message={ - - } - /> - + + } + message={ + + } + /> ); } if (clusterUpgradeState === 'isUpgrading') { return ( - - - - - } - body={ -

- + + + } + body={ +

+ -

- } - data-test-subj="emptyPrompt" - /> -
+ /> +

+ } + data-test-subj="isUpgradingMessage" + /> ); } if (clusterUpgradeState === 'isUpgradeComplete') { return ( - + + + } + body={ +

+ +

+ } data-test-subj="isUpgradeCompleteMessage" - > - - - - } - body={ -

- -

- } - data-test-subj="emptyPrompt" - /> -
+ /> ); } @@ -170,16 +144,7 @@ export const App = ({ history }: { history: ScopedHistory }) => { // Prevent flicker of the underlying UI while we wait for the status to fetch. if (isLoading && isInitialRequest) { - return ( - - } /> - - ); + return } />; } return ( diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx index e9025783a94d5..fedde5a0bdc6a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx @@ -12,7 +12,7 @@ import { EuiButtonEmpty, EuiSpacer, EuiPageBody, - EuiPageContentBody_Deprecated as EuiPageContentBody, + EuiPageSection, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; @@ -40,7 +40,7 @@ export const EsDeprecationLogs: FunctionComponent = () => { return ( - + { - + ); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx index 7674fab8be2d5..adb6feeba1cbf 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx @@ -8,13 +8,7 @@ import React, { useEffect, useMemo } from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { - EuiPageHeader, - EuiSpacer, - EuiPageContent_Deprecated as EuiPageContent, - EuiLink, - EuiCallOut, -} from '@elastic/eui'; +import { EuiPageHeader, EuiSpacer, EuiLink, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { DocLinksStart } from '@kbn/core/public'; @@ -155,21 +149,15 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { } if (isLoading) { - return ( - - {i18nTexts.isLoading} - - ); + return {i18nTexts.isLoading}; } if (esDeprecations?.deprecations?.length === 0) { return ( - - history.push('/overview')} - /> - + history.push('/overview')} + /> ); } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx index b28fa1180bda9..25e96108044c8 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx @@ -8,12 +8,7 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { - EuiPageContent_Deprecated as EuiPageContent, - EuiPageHeader, - EuiSpacer, - EuiCallOut, -} from '@elastic/eui'; +import { EuiPageHeader, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; @@ -234,21 +229,15 @@ export const KibanaDeprecations = withRouter(({ history }: RouteComponentProps) } if (isLoading) { - return ( - - {i18nTexts.isLoading} - - ); + return {i18nTexts.isLoading}; } if (kibanaDeprecations?.length === 0) { return ( - - history.push('/overview')} - /> - + history.push('/overview')} + /> ); } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx index 878f84864c302..cc28b2e152e23 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx @@ -15,7 +15,7 @@ import { EuiSpacer, EuiLink, EuiPageBody, - EuiPageContentBody_Deprecated as EuiPageContentBody, + EuiPageSection, } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; @@ -69,7 +69,7 @@ export const Overview = withRouter(({ history }: RouteComponentProps) => { return ( - + { ].filter(Boolean) as EuiStepProps[] } /> - + ); }); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecations_page_loading_error.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecations_page_loading_error.tsx index 79556766c0dfe..171dc50bcb6b8 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecations_page_loading_error.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecations_page_loading_error.tsx @@ -6,7 +6,7 @@ */ import React, { FunctionComponent } from 'react'; -import { EuiPageContent_Deprecated as EuiPageContent, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiPageTemplate } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DeprecationSource } from '../../../../common/types'; @@ -20,23 +20,18 @@ export const DeprecationsPageLoadingError: FunctionComponent = ({ deprecationSource, message, }) => ( - - - {i18n.translate('xpack.upgradeAssistant.deprecationsPageLoadingError.title', { - defaultMessage: 'Could not retrieve {deprecationSource} deprecation issues', - values: { deprecationSource }, - })} - - } - body={message} - /> - + title={ +

+ {i18n.translate('xpack.upgradeAssistant.deprecationsPageLoadingError.title', { + defaultMessage: 'Could not retrieve {deprecationSource} deprecation issues', + values: { deprecationSource }, + })} +

+ } + body={message} + /> ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx index 6e06ffe03f904..dda89bfd45f1a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent } from 'react'; -import { EuiLink, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiLink, EuiPageTemplate } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -46,7 +46,7 @@ export const NoDeprecationsPrompt: FunctionComponent = ({ navigateToOverviewPage, }) => { return ( - {i18nTexts.getEmptyPromptTitle(deprecationType)}} From 0d63919122efd1dea893f5890357dd57d207f660 Mon Sep 17 00:00:00 2001 From: Kevin Lacabane Date: Mon, 21 Aug 2023 21:06:36 +0200 Subject: [PATCH 16/29] [Asset Manager] services endpoint (#164181) Closes https://github.com/elastic/kibana/issues/159641 Implements `/assets/services` endpoint that returns service assets found in the configured source (signals or assets indices). Consumer can provide a `parent` query to filter the returned services. While the _assets_ mode supports any kind of parent/depth thanks to its common interface, the _signals_ mode only supports host parent for the moment. 1. pull this branch and point it at an oblt-cli created cluster that uses cross-cluster search to read from the edge cluster 2. add the following[1] to your kibana.yml file 3. hit `/api/asset-manager/assets/services?from=&to=&(parent=)?`. services should be returned. Add/remove parent query string to filter services only running on specific host. [1] ``` xpack.assetManager: alphaEnabled: true sourceIndices: metrics: remote_cluster:metricbeat*,remote_cluster:metrics-* logs: remote_cluster:filebeat*,remote_cluster:logs-* traces: remote_cluster:traces-* serviceMetrics: remote_cluster:metrics-apm* serviceLogs: remote_cluster:logs-apm* lockedSource: signals ``` Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/accessors/hosts/index.ts | 4 +- .../services/get_services_by_assets.ts | 46 ++++++++ .../services/get_services_by_signals.ts | 38 +++++++ .../server/lib/accessors/services/index.ts | 22 ++++ .../server/lib/asset_accessor.ts | 12 ++ .../server/lib/collectors/index.ts | 5 +- .../server/lib/collectors/services.ts | 29 +++-- .../server/routes/assets/hosts.ts | 4 +- .../server/routes/assets/services.ts | 70 ++++++++++++ .../asset_manager/server/routes/index.ts | 2 + .../tests/with_signals_source/services.ts | 107 ++++++++++++++++++ 11 files changed, 321 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_assets.ts create mode 100644 x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_signals.ts create mode 100644 x-pack/plugins/asset_manager/server/lib/accessors/services/index.ts create mode 100644 x-pack/plugins/asset_manager/server/routes/assets/services.ts create mode 100644 x-pack/test/api_integration/apis/asset_manager/tests/with_signals_source/services.ts diff --git a/x-pack/plugins/asset_manager/server/lib/accessors/hosts/index.ts b/x-pack/plugins/asset_manager/server/lib/accessors/hosts/index.ts index 6198824ba8a02..9becb15ebdc0a 100644 --- a/x-pack/plugins/asset_manager/server/lib/accessors/hosts/index.ts +++ b/x-pack/plugins/asset_manager/server/lib/accessors/hosts/index.ts @@ -8,8 +8,8 @@ import { AccessorOptions, OptionsWithInjectedValues } from '..'; export interface GetHostsOptions extends AccessorOptions { - from: number; - to: number; + from: string; + to: string; } export type GetHostsOptionsInjected = OptionsWithInjectedValues; diff --git a/x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_assets.ts b/x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_assets.ts new file mode 100644 index 0000000000000..8bdd6283d6559 --- /dev/null +++ b/x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_assets.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Asset } from '../../../../common/types_api'; +import { GetServicesOptionsInjected } from '.'; +import { getAssets } from '../../get_assets'; +import { getAllRelatedAssets } from '../../get_all_related_assets'; + +export async function getServicesByAssets( + options: GetServicesOptionsInjected +): Promise<{ services: Asset[] }> { + if (options.parent) { + return getServicesByParent(options); + } + + const services = await getAssets({ + esClient: options.esClient, + filters: { + kind: 'service', + from: options.from, + to: options.to, + }, + }); + + return { services }; +} + +async function getServicesByParent( + options: GetServicesOptionsInjected +): Promise<{ services: Asset[] }> { + const { descendants } = await getAllRelatedAssets(options.esClient, { + from: options.from, + to: options.to, + maxDistance: 5, + kind: ['service'], + size: 100, + relation: 'descendants', + ean: options.parent!, + }); + + return { services: descendants as Asset[] }; +} diff --git a/x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_signals.ts b/x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_signals.ts new file mode 100644 index 0000000000000..e368ec97e9aaf --- /dev/null +++ b/x-pack/plugins/asset_manager/server/lib/accessors/services/get_services_by_signals.ts @@ -0,0 +1,38 @@ +/* + * 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 { Asset } from '../../../../common/types_api'; +import { GetServicesOptionsInjected } from '.'; +import { collectServices } from '../../collectors/services'; + +export async function getServicesBySignals( + options: GetServicesOptionsInjected +): Promise<{ services: Asset[] }> { + const filters = []; + + if (options.parent) { + filters.push({ + bool: { + should: [ + { term: { 'host.name': options.parent } }, + { term: { 'host.hostname': options.parent } }, + ], + minimum_should_match: 1, + }, + }); + } + + const { assets } = await collectServices({ + client: options.esClient, + from: options.from, + to: options.to, + sourceIndices: options.sourceIndices, + filters, + }); + + return { services: assets }; +} diff --git a/x-pack/plugins/asset_manager/server/lib/accessors/services/index.ts b/x-pack/plugins/asset_manager/server/lib/accessors/services/index.ts new file mode 100644 index 0000000000000..3fed1047eacba --- /dev/null +++ b/x-pack/plugins/asset_manager/server/lib/accessors/services/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AccessorOptions, OptionsWithInjectedValues } from '..'; + +export interface GetServicesOptions extends AccessorOptions { + from: string; + to: string; + parent?: string; +} +export type GetServicesOptionsInjected = OptionsWithInjectedValues; + +export interface ServiceIdentifier { + 'asset.ean': string; + 'asset.id': string; + 'asset.name'?: string; + 'service.environment'?: string; +} diff --git a/x-pack/plugins/asset_manager/server/lib/asset_accessor.ts b/x-pack/plugins/asset_manager/server/lib/asset_accessor.ts index d31173d8d8c69..5710ec562bac1 100644 --- a/x-pack/plugins/asset_manager/server/lib/asset_accessor.ts +++ b/x-pack/plugins/asset_manager/server/lib/asset_accessor.ts @@ -9,8 +9,11 @@ import { Asset } from '../../common/types_api'; import { AssetManagerConfig } from '../types'; import { OptionsWithInjectedValues } from './accessors'; import { GetHostsOptions } from './accessors/hosts'; +import { GetServicesOptions } from './accessors/services'; import { getHostsByAssets } from './accessors/hosts/get_hosts_by_assets'; import { getHostsBySignals } from './accessors/hosts/get_hosts_by_signals'; +import { getServicesByAssets } from './accessors/services/get_services_by_assets'; +import { getServicesBySignals } from './accessors/services/get_services_by_signals'; interface AssetAccessorClassOptions { sourceIndices: AssetManagerConfig['sourceIndices']; @@ -35,4 +38,13 @@ export class AssetAccessor { return await getHostsBySignals(withInjected); } } + + async getServices(options: GetServicesOptions): Promise<{ services: Asset[] }> { + const withInjected = this.injectOptions(options); + if (this.options.source === 'assets') { + return await getServicesByAssets(withInjected); + } else { + return await getServicesBySignals(withInjected); + } + } } diff --git a/x-pack/plugins/asset_manager/server/lib/collectors/index.ts b/x-pack/plugins/asset_manager/server/lib/collectors/index.ts index 0fef13d74b7fc..5e0b300e601db 100644 --- a/x-pack/plugins/asset_manager/server/lib/collectors/index.ts +++ b/x-pack/plugins/asset_manager/server/lib/collectors/index.ts @@ -16,10 +16,11 @@ export type Collector = (opts: CollectorOptions) => Promise; export interface CollectorOptions { client: ElasticsearchClient; - from: number; - to: number; + from: string; + to: string; sourceIndices: AssetManagerConfig['sourceIndices']; afterKey?: estypes.SortResults; + filters?: estypes.QueryDslQueryContainer[]; } export interface CollectorResult { diff --git a/x-pack/plugins/asset_manager/server/lib/collectors/services.ts b/x-pack/plugins/asset_manager/server/lib/collectors/services.ts index 04d3b9c472a3e..c351f49f3a8f7 100644 --- a/x-pack/plugins/asset_manager/server/lib/collectors/services.ts +++ b/x-pack/plugins/asset_manager/server/lib/collectors/services.ts @@ -15,8 +15,18 @@ export async function collectServices({ to, sourceIndices, afterKey, + filters = [], }: CollectorOptions) { const { traces, serviceMetrics, serviceLogs } = sourceIndices; + const musts: estypes.QueryDslQueryContainer[] = [ + ...filters, + { + exists: { + field: 'service.name', + }, + }, + ]; + const dsl: estypes.SearchRequest = { index: [traces, serviceMetrics, serviceLogs], size: 0, @@ -33,13 +43,7 @@ export async function collectServices({ }, }, ], - must: [ - { - exists: { - field: 'service.name', - }, - }, - ], + must: musts, }, }, aggs: { @@ -58,6 +62,7 @@ export async function collectServices({ serviceEnvironment: { terms: { field: 'service.environment', + missing_bucket: true, }, }, }, @@ -112,14 +117,14 @@ export async function collectServices({ } containerHosts.buckets?.forEach((containerBucket: any) => { - const [containerId, hostname] = containerBucket.key; - if (containerId) { - (service['asset.parents'] as string[]).push(`container:${containerId}`); - } - + const [hostname, containerId] = containerBucket.key; if (hostname) { (service['asset.references'] as string[]).push(`host:${hostname}`); } + + if (containerId) { + (service['asset.parents'] as string[]).push(`container:${containerId}`); + } }); acc.push(service); diff --git a/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts b/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts index 19e3a7abe59f2..2b1088990334c 100644 --- a/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts +++ b/x-pack/plugins/asset_manager/server/routes/assets/hosts.ts @@ -50,8 +50,8 @@ export function hostsRoutes({ try { const response = await assetAccessor.getHosts({ - from: datemath.parse(from)!.valueOf(), - to: datemath.parse(to)!.valueOf(), + from: datemath.parse(from)!.toISOString(), + to: datemath.parse(to)!.toISOString(), esClient, }); diff --git a/x-pack/plugins/asset_manager/server/routes/assets/services.ts b/x-pack/plugins/asset_manager/server/routes/assets/services.ts new file mode 100644 index 0000000000000..60f282a219c05 --- /dev/null +++ b/x-pack/plugins/asset_manager/server/routes/assets/services.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 * as rt from 'io-ts'; +import datemath from '@kbn/datemath'; +import { + dateRt, + inRangeFromStringRt, + datemathStringRt, + createRouteValidationFunction, + createLiteralValueFromUndefinedRT, +} from '@kbn/io-ts-utils'; +import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; +import { debug } from '../../../common/debug_log'; +import { SetupRouteOptions } from '../types'; +import { ASSET_MANAGER_API_BASE } from '../../constants'; +import { getEsClientFromContext } from '../utils'; + +const sizeRT = rt.union([inRangeFromStringRt(1, 100), createLiteralValueFromUndefinedRT(10)]); +const assetDateRT = rt.union([dateRt, datemathStringRt]); +const getServiceAssetsQueryOptionsRT = rt.exact( + rt.partial({ + from: assetDateRT, + to: assetDateRT, + size: sizeRT, + parent: rt.string, + }) +); + +export type GetServiceAssetsQueryOptions = rt.TypeOf; + +export function servicesRoutes({ + router, + assetAccessor, +}: SetupRouteOptions) { + // GET /assets/services + router.get( + { + path: `${ASSET_MANAGER_API_BASE}/assets/services`, + validate: { + query: createRouteValidationFunction(getServiceAssetsQueryOptionsRT), + }, + }, + async (context, req, res) => { + const { from = 'now-24h', to = 'now', parent } = req.query || {}; + const esClient = await getEsClientFromContext(context); + + try { + const response = await assetAccessor.getServices({ + from: datemath.parse(from)!.toISOString(), + to: datemath.parse(to)!.toISOString(), + parent, + esClient, + }); + + return res.ok({ body: response }); + } catch (error: unknown) { + debug('Error while looking up SERVICE asset records', error); + return res.customError({ + statusCode: 500, + body: { message: 'Error while looking up service asset records - ' + `${error}` }, + }); + } + } + ); +} diff --git a/x-pack/plugins/asset_manager/server/routes/index.ts b/x-pack/plugins/asset_manager/server/routes/index.ts index 4c80215ae5b85..cab0b1558fa00 100644 --- a/x-pack/plugins/asset_manager/server/routes/index.ts +++ b/x-pack/plugins/asset_manager/server/routes/index.ts @@ -11,6 +11,7 @@ import { pingRoute } from './ping'; import { assetsRoutes } from './assets'; import { sampleAssetsRoutes } from './sample_assets'; import { hostsRoutes } from './assets/hosts'; +import { servicesRoutes } from './assets/services'; export function setupRoutes({ router, @@ -20,4 +21,5 @@ export function setupRoutes({ assetsRoutes({ router, assetAccessor }); sampleAssetsRoutes({ router, assetAccessor }); hostsRoutes({ router, assetAccessor }); + servicesRoutes({ router, assetAccessor }); } diff --git a/x-pack/test/api_integration/apis/asset_manager/tests/with_signals_source/services.ts b/x-pack/test/api_integration/apis/asset_manager/tests/with_signals_source/services.ts new file mode 100644 index 0000000000000..320f4d4c0fc50 --- /dev/null +++ b/x-pack/test/api_integration/apis/asset_manager/tests/with_signals_source/services.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { omit } from 'lodash'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { ASSETS_ENDPOINT } from '../constants'; +import { FtrProviderContext } from '../../types'; + +const SERVICES_ASSETS_ENDPOINT = `${ASSETS_ENDPOINT}/services`; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const synthtrace = getService('apmSynthtraceEsClient'); + + describe('asset management', () => { + beforeEach(async () => { + await synthtrace.clean(); + }); + + describe('GET /assets/services', () => { + it('should return services', async () => { + const from = new Date(Date.now() - 1000 * 60 * 2).toISOString(); + const to = new Date().toISOString(); + await synthtrace.index(generateServicesData({ from, to, count: 2 })); + + const response = await supertest + .get(SERVICES_ASSETS_ENDPOINT) + .query({ + from, + to, + }) + .expect(200); + + expect(response.body).to.have.property('services'); + expect(response.body.services.length).to.equal(2); + }); + + it('should return services running on specified host', async () => { + const from = new Date(Date.now() - 1000 * 60 * 2).toISOString(); + const to = new Date().toISOString(); + await synthtrace.index(generateServicesData({ from, to, count: 5 })); + + const response = await supertest + .get(SERVICES_ASSETS_ENDPOINT) + .query({ + from, + to, + parent: 'my-host-1', + }) + .expect(200); + + expect(response.body).to.have.property('services'); + expect(response.body.services.length).to.equal(1); + expect(omit(response.body.services[0], ['@timestamp'])).to.eql({ + 'asset.kind': 'service', + 'asset.id': 'service-1', + 'asset.ean': 'service:service-1', + 'asset.references': [], + 'asset.parents': [], + 'service.environment': 'production', + }); + }); + }); + }); +} + +function generateServicesData({ + from, + to, + count = 1, +}: { + from: string; + to: string; + count: number; +}) { + const range = timerange(from, to); + + const services = Array(count) + .fill(0) + .map((_, idx) => + apm + .service({ + name: `service-${idx}`, + environment: 'production', + agentName: 'nodejs', + }) + .instance(`my-host-${idx}`) + ); + + return range + .interval('1m') + .rate(1) + .generator((timestamp, index) => + services.map((service) => + service + .transaction({ transactionName: 'GET /foo' }) + .timestamp(timestamp) + .duration(500) + .success() + ) + ); +} From 5a68f709007e15edad0dda615b63d34b7854b87d Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Mon, 21 Aug 2023 12:38:24 -0700 Subject: [PATCH 17/29] Update core architecture docs (#164120) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> --- bundle/bundled.json | 326 ++++++++++++++++++ bundle/bundled.yaml | 209 +++++++++++ bundle/entrypoint.json | 326 ++++++++++++++++++ bundle/entrypoint.yaml | 209 +++++++++++ .../core/application_service.asciidoc | 2 +- .../core/configuration-service.asciidoc | 2 +- .../architecture/core/http-service.asciidoc | 4 +- .../architecture/core/index.asciidoc | 4 + .../architecture/core/packages.asciidoc | 26 ++ .../core/patterns-scoped-services.asciidoc | 14 +- .../core/uisettings-service.asciidoc | 9 +- docs/developer/architecture/index.asciidoc | 2 + 12 files changed, 1116 insertions(+), 17 deletions(-) create mode 100644 bundle/bundled.json create mode 100644 bundle/bundled.yaml create mode 100644 bundle/entrypoint.json create mode 100644 bundle/entrypoint.yaml create mode 100644 docs/developer/architecture/core/packages.asciidoc diff --git a/bundle/bundled.json b/bundle/bundled.json new file mode 100644 index 0000000000000..21c305d6f4216 --- /dev/null +++ b/bundle/bundled.json @@ -0,0 +1,326 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Saved objects", + "description": "OpenAPI schema for saved object endpoints", + "version": "0.1", + "contact": { + "name": "Kibana Core Team" + }, + "license": { + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" + } + }, + "servers": [ + { + "url": "http://localhost:5601", + "description": "local" + } + ], + "security": [ + { + "basicAuth": [] + }, + { + "apiKeyAuth": [] + } + ], + "tags": [ + { + "name": "saved objects", + "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." + } + ], + "paths": { + "/api/saved_objects/_export": { + "post": { + "summary": "Retrieve sets of saved objects that you want to import into Kibana.", + "operationId": "exportSavedObjects", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "anyOf": [ + { + "required": [ + "type" + ] + }, + { + "required": [ + "objects" + ] + } + ], + "properties": { + "excludeExportDetails": { + "description": "Do not add export details entry at the end of the stream.", + "type": "boolean", + "default": false + }, + "includeReferencesDeep": { + "description": "Includes all of the referenced objects in the exported objects.", + "type": "boolean" + }, + "objects": { + "description": "A list of objects to export.", + "type": "array", + "items": { + "type": "object" + } + }, + "type": { + "description": "The saved object types to include in the export.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "exportSavedObjectsRequest": { + "$ref": "#/components/examples/export_objects_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "multipart/form-data": { + "schema": { + "type": "string" + }, + "examples": { + "exportSavedObjectsResponse": { + "$ref": "#/components/examples/export_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/api/saved_objects/_import": { + "post": { + "summary": "Create sets of Kibana saved objects from a file created by the export API.", + "operationId": "importSavedObjects", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "in": "query", + "name": "compatibilityMode", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option.\n" + }, + { + "in": "query", + "name": "createNewCopies", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options.\n" + }, + { + "in": "query", + "name": "overwrite", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option.\n" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "description": "A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported.\n" + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "description": "Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error.\n" + }, + "success": { + "type": "boolean", + "description": "Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties.\n" + }, + "successCount": { + "type": "integer", + "description": "Indicates the number of successfully imported records." + }, + "successResults": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute.\n" + }, + "warnings": { + "type": "array" + } + } + }, + "examples": { + "importObjectsResponse": { + "$ref": "#/components/examples/import_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + } + }, + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "ApiKey" + } + }, + "parameters": { + "kbn_xsrf": { + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "description": "Cross-site request forgery protection", + "required": true + } + }, + "examples": { + "export_objects_request": { + "summary": "Export a specific saved object.", + "value": { + "objects": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "includeReferencesDeep": false + } + }, + "export_objects_response": { + "summary": "The export objects API response contains a JSON record for each exported object and an export result details record.", + "value": "{\"attributes\":{\"fieldFormatMap\":\"{\\\"hour_of_day\\\":{}}\",\"name\":\"Kibana Sample Data Logs\",\"runtimeFieldMap\":\"{\\\"hour_of_day\\\":{\\\"type\\\":\\\"long\\\",\\\"script\\\":{\\\"source\\\":\\\"emit(doc['timestamp'].value.getHour());\\\"}}}\",\"timeFieldName\":\"timestamp\",\"title\":\"kibana_sample_data_logs\"},\"coreMigrationVersion\":\"8.8.0\",\"created_at\":\"2023-07-25T19:36:36.695Z\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"managed\":false,\"references\":[],\"type\":\"index-pattern\",\"typeMigrationVersion\":\"8.0.0\",\"updated_at\":\"2023-07-25T19:36:36.695Z\",\"version\":\"WzM5LDJd\"}\n{\"excludedObjects\":[],\"excludedObjectsCount\":0,\"exportedCount\":1,\"missingRefCount\":0,\"missingReferences\":[]}\n" + }, + "import_objects_response": { + "summary": "The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute.", + "value": { + "successCount": 1, + "success": true, + "warnings": [], + "successResults": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "meta": { + "title": "Kibana Sample Data Logs", + "icon": "indexPatternApp" + }, + "managed": false, + "destinationId": "82d2760c-468f-49cf-83aa-b9a35b6a8943" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/bundle/bundled.yaml b/bundle/bundled.yaml new file mode 100644 index 0000000000000..0facbd5446640 --- /dev/null +++ b/bundle/bundled.yaml @@ -0,0 +1,209 @@ +openapi: 3.1.0 +info: + title: Saved objects + description: OpenAPI schema for saved object endpoints + version: '0.1' + contact: + name: Kibana Core Team + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +servers: + - url: http://localhost:5601 + description: local +security: + - basicAuth: [] + - apiKeyAuth: [] +tags: + - name: saved objects + description: Manage Kibana saved objects, including dashboards, visualizations, and more. +paths: + /api/saved_objects/_export: + post: + summary: Retrieve sets of saved objects that you want to import into Kibana. + operationId: exportSavedObjects + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + anyOf: + - required: + - type + - required: + - objects + properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: Includes all of the referenced objects in the exported objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: The saved object types to include in the export. + oneOf: + - type: string + - type: array + items: + type: string + examples: + exportSavedObjectsRequest: + $ref: '#/components/examples/export_objects_request' + responses: + '200': + description: Indicates a successful call. + content: + multipart/form-data: + schema: + type: string + examples: + exportSavedObjectsResponse: + $ref: '#/components/examples/export_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + type: object + additionalProperties: true + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 + /api/saved_objects/_import: + post: + summary: Create sets of Kibana saved objects from a file created by the export API. + operationId: importSavedObjects + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - in: query + name: compatibilityMode + schema: + type: boolean + required: false + description: | + Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. + - in: query + name: createNewCopies + schema: + type: boolean + required: false + description: | + Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. + - in: query + name: overwrite + schema: + type: boolean + required: false + description: | + Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + description: | + A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + errors: + type: array + description: | + Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. + success: + type: boolean + description: | + Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + successResults: + type: array + items: + type: object + description: | + Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. + warnings: + type: array + examples: + importObjectsResponse: + $ref: '#/components/examples/import_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + type: object + additionalProperties: true + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + apiKeyAuth: + type: apiKey + in: header + name: ApiKey + parameters: + kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + examples: + export_objects_request: + summary: Export a specific saved object. + value: + objects: + - type: index-pattern + id: 90943e30-9a47-11e8-b64d-95841ca0b247 + includeReferencesDeep: false + export_objects_response: + summary: The export objects API response contains a JSON record for each exported object and an export result details record. + value: | + {"attributes":{"fieldFormatMap":"{\"hour_of_day\":{}}","name":"Kibana Sample Data Logs","runtimeFieldMap":"{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}","timeFieldName":"timestamp","title":"kibana_sample_data_logs"},"coreMigrationVersion":"8.8.0","created_at":"2023-07-25T19:36:36.695Z","id":"90943e30-9a47-11e8-b64d-95841ca0b247","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2023-07-25T19:36:36.695Z","version":"WzM5LDJd"} + {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} + import_objects_response: + summary: The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute. + value: + successCount: 1 + success: true + warnings: [] + successResults: + - type: index-pattern + id: 90943e30-9a47-11e8-b64d-95841ca0b247 + meta: + title: Kibana Sample Data Logs + icon: indexPatternApp + managed: false + destinationId: 82d2760c-468f-49cf-83aa-b9a35b6a8943 diff --git a/bundle/entrypoint.json b/bundle/entrypoint.json new file mode 100644 index 0000000000000..21c305d6f4216 --- /dev/null +++ b/bundle/entrypoint.json @@ -0,0 +1,326 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Saved objects", + "description": "OpenAPI schema for saved object endpoints", + "version": "0.1", + "contact": { + "name": "Kibana Core Team" + }, + "license": { + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" + } + }, + "servers": [ + { + "url": "http://localhost:5601", + "description": "local" + } + ], + "security": [ + { + "basicAuth": [] + }, + { + "apiKeyAuth": [] + } + ], + "tags": [ + { + "name": "saved objects", + "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." + } + ], + "paths": { + "/api/saved_objects/_export": { + "post": { + "summary": "Retrieve sets of saved objects that you want to import into Kibana.", + "operationId": "exportSavedObjects", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "anyOf": [ + { + "required": [ + "type" + ] + }, + { + "required": [ + "objects" + ] + } + ], + "properties": { + "excludeExportDetails": { + "description": "Do not add export details entry at the end of the stream.", + "type": "boolean", + "default": false + }, + "includeReferencesDeep": { + "description": "Includes all of the referenced objects in the exported objects.", + "type": "boolean" + }, + "objects": { + "description": "A list of objects to export.", + "type": "array", + "items": { + "type": "object" + } + }, + "type": { + "description": "The saved object types to include in the export.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "exportSavedObjectsRequest": { + "$ref": "#/components/examples/export_objects_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "multipart/form-data": { + "schema": { + "type": "string" + }, + "examples": { + "exportSavedObjectsResponse": { + "$ref": "#/components/examples/export_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "/api/saved_objects/_import": { + "post": { + "summary": "Create sets of Kibana saved objects from a file created by the export API.", + "operationId": "importSavedObjects", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "in": "query", + "name": "compatibilityMode", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option.\n" + }, + { + "in": "query", + "name": "createNewCopies", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options.\n" + }, + { + "in": "query", + "name": "overwrite", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option.\n" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "description": "A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported.\n" + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "description": "Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error.\n" + }, + "success": { + "type": "boolean", + "description": "Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties.\n" + }, + "successCount": { + "type": "integer", + "description": "Indicates the number of successfully imported records." + }, + "successResults": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute.\n" + }, + "warnings": { + "type": "array" + } + } + }, + "examples": { + "importObjectsResponse": { + "$ref": "#/components/examples/import_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + } + }, + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + }, + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "ApiKey" + } + }, + "parameters": { + "kbn_xsrf": { + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "description": "Cross-site request forgery protection", + "required": true + } + }, + "examples": { + "export_objects_request": { + "summary": "Export a specific saved object.", + "value": { + "objects": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "includeReferencesDeep": false + } + }, + "export_objects_response": { + "summary": "The export objects API response contains a JSON record for each exported object and an export result details record.", + "value": "{\"attributes\":{\"fieldFormatMap\":\"{\\\"hour_of_day\\\":{}}\",\"name\":\"Kibana Sample Data Logs\",\"runtimeFieldMap\":\"{\\\"hour_of_day\\\":{\\\"type\\\":\\\"long\\\",\\\"script\\\":{\\\"source\\\":\\\"emit(doc['timestamp'].value.getHour());\\\"}}}\",\"timeFieldName\":\"timestamp\",\"title\":\"kibana_sample_data_logs\"},\"coreMigrationVersion\":\"8.8.0\",\"created_at\":\"2023-07-25T19:36:36.695Z\",\"id\":\"90943e30-9a47-11e8-b64d-95841ca0b247\",\"managed\":false,\"references\":[],\"type\":\"index-pattern\",\"typeMigrationVersion\":\"8.0.0\",\"updated_at\":\"2023-07-25T19:36:36.695Z\",\"version\":\"WzM5LDJd\"}\n{\"excludedObjects\":[],\"excludedObjectsCount\":0,\"exportedCount\":1,\"missingRefCount\":0,\"missingReferences\":[]}\n" + }, + "import_objects_response": { + "summary": "The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute.", + "value": { + "successCount": 1, + "success": true, + "warnings": [], + "successResults": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "meta": { + "title": "Kibana Sample Data Logs", + "icon": "indexPatternApp" + }, + "managed": false, + "destinationId": "82d2760c-468f-49cf-83aa-b9a35b6a8943" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/bundle/entrypoint.yaml b/bundle/entrypoint.yaml new file mode 100644 index 0000000000000..0facbd5446640 --- /dev/null +++ b/bundle/entrypoint.yaml @@ -0,0 +1,209 @@ +openapi: 3.1.0 +info: + title: Saved objects + description: OpenAPI schema for saved object endpoints + version: '0.1' + contact: + name: Kibana Core Team + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +servers: + - url: http://localhost:5601 + description: local +security: + - basicAuth: [] + - apiKeyAuth: [] +tags: + - name: saved objects + description: Manage Kibana saved objects, including dashboards, visualizations, and more. +paths: + /api/saved_objects/_export: + post: + summary: Retrieve sets of saved objects that you want to import into Kibana. + operationId: exportSavedObjects + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + anyOf: + - required: + - type + - required: + - objects + properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: Includes all of the referenced objects in the exported objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: The saved object types to include in the export. + oneOf: + - type: string + - type: array + items: + type: string + examples: + exportSavedObjectsRequest: + $ref: '#/components/examples/export_objects_request' + responses: + '200': + description: Indicates a successful call. + content: + multipart/form-data: + schema: + type: string + examples: + exportSavedObjectsResponse: + $ref: '#/components/examples/export_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + type: object + additionalProperties: true + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 + /api/saved_objects/_import: + post: + summary: Create sets of Kibana saved objects from a file created by the export API. + operationId: importSavedObjects + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - in: query + name: compatibilityMode + schema: + type: boolean + required: false + description: | + Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. + - in: query + name: createNewCopies + schema: + type: boolean + required: false + description: | + Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. + - in: query + name: overwrite + schema: + type: boolean + required: false + description: | + Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + description: | + A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + errors: + type: array + description: | + Indicates the import was unsuccessful and specifies the objects that failed to import. One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. + success: + type: boolean + description: | + Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + successResults: + type: array + items: + type: object + description: | + Indicates the objects that are successfully imported, with any metadata if applicable. Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. + warnings: + type: array + examples: + importObjectsResponse: + $ref: '#/components/examples/import_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + type: object + additionalProperties: true + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + apiKeyAuth: + type: apiKey + in: header + name: ApiKey + parameters: + kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + examples: + export_objects_request: + summary: Export a specific saved object. + value: + objects: + - type: index-pattern + id: 90943e30-9a47-11e8-b64d-95841ca0b247 + includeReferencesDeep: false + export_objects_response: + summary: The export objects API response contains a JSON record for each exported object and an export result details record. + value: | + {"attributes":{"fieldFormatMap":"{\"hour_of_day\":{}}","name":"Kibana Sample Data Logs","runtimeFieldMap":"{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}","timeFieldName":"timestamp","title":"kibana_sample_data_logs"},"coreMigrationVersion":"8.8.0","created_at":"2023-07-25T19:36:36.695Z","id":"90943e30-9a47-11e8-b64d-95841ca0b247","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2023-07-25T19:36:36.695Z","version":"WzM5LDJd"} + {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} + import_objects_response: + summary: The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute. + value: + successCount: 1 + success: true + warnings: [] + successResults: + - type: index-pattern + id: 90943e30-9a47-11e8-b64d-95841ca0b247 + meta: + title: Kibana Sample Data Logs + icon: indexPatternApp + managed: false + destinationId: 82d2760c-468f-49cf-83aa-b9a35b6a8943 diff --git a/docs/developer/architecture/core/application_service.asciidoc b/docs/developer/architecture/core/application_service.asciidoc index 2aa50ebafad8d..31b0a3d2a4356 100644 --- a/docs/developer/architecture/core/application_service.asciidoc +++ b/docs/developer/architecture/core/application_service.asciidoc @@ -29,7 +29,7 @@ export class MyPlugin implements Plugin { } } ---- -<1> See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[application.register interface] +<1> Refer to {kib-repo}/blob/8.9/packages/core/application/core-application-browser/src/contracts.ts[application.register interface] <2> Application specific URL. <3> `mount` callback is invoked when a user navigates to the application-specific URL. <4> `core.getStartServices` method provides API available during `start` lifecycle. diff --git a/docs/developer/architecture/core/configuration-service.asciidoc b/docs/developer/architecture/core/configuration-service.asciidoc index fdc4ad9b0a922..570bea37ddf09 100644 --- a/docs/developer/architecture/core/configuration-service.asciidoc +++ b/docs/developer/architecture/core/configuration-service.asciidoc @@ -17,7 +17,7 @@ const basePath = core.http.basePath.get(request); To have access to your plugin config, you _should_: * Declare plugin-specific `configPath` (will fallback to plugin `id` -if not specified) in {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file. +if not specified) in your plugin definition. * Export schema validation for the config from plugin's main file. Schema is mandatory. If a plugin reads from the config without schema declaration, `ConfigService` will throw an error. diff --git a/docs/developer/architecture/core/http-service.asciidoc b/docs/developer/architecture/core/http-service.asciidoc index 6cc171aa7969a..da4f0a3f7f5e1 100644 --- a/docs/developer/architecture/core/http-service.asciidoc +++ b/docs/developer/architecture/core/http-service.asciidoc @@ -12,7 +12,7 @@ The service allows plugins to: * to execute custom logic on an incoming request or server response. * implement custom authentication and authorization strategy. -See {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md[HTTP service API docs] +Refer to {kib-repo}/blob/8.9/packages/core/http/core-http-server/src/http_contract.ts[HTTP service contract types]. [source,typescript] ---- @@ -64,4 +64,4 @@ async function fetchData(core: CoreStart) { ); } ---- -See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[for all available API]. +Refer to {kib-repo}/blob/8.9/packages/core/http/core-http-browser/src/types.ts[the client-side APIs]. diff --git a/docs/developer/architecture/core/index.asciidoc b/docs/developer/architecture/core/index.asciidoc index b03e98ffbb57d..e866fefb38530 100644 --- a/docs/developer/architecture/core/index.asciidoc +++ b/docs/developer/architecture/core/index.asciidoc @@ -27,6 +27,8 @@ export class MyPlugin { } ---- + + The services that core provides are: * <> @@ -36,3 +38,5 @@ The services that core provides are: * <> * <> * <> + +NOTE: Core provides the {kib} building blocks for plugins and is implemented as a collection of <>. diff --git a/docs/developer/architecture/core/packages.asciidoc b/docs/developer/architecture/core/packages.asciidoc new file mode 100644 index 0000000000000..bc544e3540d2e --- /dev/null +++ b/docs/developer/architecture/core/packages.asciidoc @@ -0,0 +1,26 @@ +[[core-packages]] +== Core packages + +experimental[] + +Core packages have well defined boundaries, have a single responsibility, and are organized by domain. Core packages follow a specific naming schema, according to what they contain: + +For example, core capapability packages are: + +* `core-capabilities-browser-internal` +* `core-capabilities-browser-mocks` +* `core-capabilities-common` +* `core-capabilities-server` +* `core-capabilities-server-internal` +* `core-capabilities-server-mocks` + +Each domain has a specific package for public types, which can be imported and used throughout the Kibana codebase including in its implementation and unit tests. These packages are internal to core and not intended for public use, but they can be used by plugins to create mock versions for unit testing. + +In addition, domains contain separate packages for the client-side and server-side and, in some cases, a base, that +supports both client and server needs. When a domain shares code between the server and client, that code lives in +a `common` package. Mocks have their own dedicated package. + +All of core's public API's have inline `jsdocs` that include examples as nescessary. + + + diff --git a/docs/developer/architecture/core/patterns-scoped-services.asciidoc b/docs/developer/architecture/core/patterns-scoped-services.asciidoc index bfd8b42dbb951..ba993a6da3af3 100644 --- a/docs/developer/architecture/core/patterns-scoped-services.asciidoc +++ b/docs/developer/architecture/core/patterns-scoped-services.asciidoc @@ -7,9 +7,7 @@ should perform a check whether an end-user has access to the data. The Kibana Platform introduced a handler interface on the server-side to perform that association internally. Core services, that require impersonation with an incoming request, are exposed via `context` argument of -{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandler.md[the -request handler interface.] -as +{kib-repo}/blob/8.9/packages/core/http/core-http-server/src/router/request_handler.ts[the request handler interface]. [source,js] ---- @@ -18,13 +16,11 @@ async function handler(context, req, res) { } ---- -The -{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md[request -handler context] exposes the following scoped *core* services: +The {kib-repo}/blob/8.9/packages/core/http/core-http-server/src/router/request_handler.ts[request handler context] exposes the following scoped *core* services: -* {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md[`context.savedObjects.client`] -* {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`] -* {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.uiSettings.client`] +* {kib-repo}/blob/8.9/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts[`context.savedObjects.client`] +* {kib-repo}/blob/8.9/packages/core/elasticsearch/core-elasticsearch-server/src/client/scoped_cluster_client.ts[`context.elasticsearch.client`] +* {kib-repo}/blob/8.9/packages/core/ui-settings/core-ui-settings-server/src/ui_settings_client.ts[`context.uiSettings.client`] ==== Declare a custom scoped service diff --git a/docs/developer/architecture/core/uisettings-service.asciidoc b/docs/developer/architecture/core/uisettings-service.asciidoc index 2d24465b69c32..f70180b72d928 100644 --- a/docs/developer/architecture/core/uisettings-service.asciidoc +++ b/docs/developer/architecture/core/uisettings-service.asciidoc @@ -38,9 +38,10 @@ uiSettings: [[client-side-usage]] === Client side usage -On the client, the `uiSettings` service is exposed directly from `core` and the {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.md[client] provides plugins access to the `config` entries stored in {es}. +On the client, the `uiSettings` service is exposed directly from `core` and the {kib-repo}/blob/8.9/packages/core/ui-settings/core-ui-settings-server/src/ui_settings_client.ts[client] +provides plugins access to the `config` entries stored in {es}. -In the interest of performance, `uiSettings` are cached. Any changes that require cache refreshes should register an instruction to reload the page when settings are configured in Advanced Settings using the `requiresPageReload` {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md[parameter]. +In the interest of performance, `uiSettings` are cached. Any changes that require cache refreshes should register an instruction to reload the page when settings are configured in Advanced Settings using the `requiresPageReload` {kib-repo}/blob/8.9/packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts[parameter]. [source,typescript] ---- @@ -77,7 +78,7 @@ export class MyPlugin implements Plugin { === Server side usage On the server, `uiSettings` are exposed directly from `core`. -The following example shows how to {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[register] a new `custom` setting with a default value of '42'. When registering a new setting, you must provide a schema against which validations are performed on read and write. All the other {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md[parameters] are optional. +The following example shows how to register a new `custom` setting with a default value of '42'. When registering a new setting, you must provide a schema against which validations are performed on read and write. All the other {kib-repo}/blob/8.9/packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts[parameters] are optional. [source,typescript] ---- @@ -114,7 +115,7 @@ export class MyPlugin implements Plugin { Migrations for 3rd party plugin advanced settings are not currently supported. If a 3rd party plugin registers an advanced setting, the setting is essentially permanent and cannot be fixed without manual intervention. ============================================== -To change or remove a `uiSetting`, the whole `config` Saved Object needs to be migrated. `uiSettings` {kib-repo}blob/{branch}/src/core/server/ui_settings/saved_objects/migrations.ts[migrations] are declared directly in the service. +To change or remove a `uiSetting`, the whole `config` Saved Object needs to be migrated. For example, if we wanted to remove a `custom` setting, or rename `my_setting:fourtyTwo` to `my_other_setting:fourtyTwo`, we'd need two migration entries, one for each change targeting the version in which these changes apply: diff --git a/docs/developer/architecture/index.asciidoc b/docs/developer/architecture/index.asciidoc index 90a0972d65f2f..36be54a384b17 100644 --- a/docs/developer/architecture/index.asciidoc +++ b/docs/developer/architecture/index.asciidoc @@ -48,6 +48,8 @@ include::core/uisettings-service.asciidoc[leveloffset=+1] include::core/patterns-scoped-services.asciidoc[leveloffset=+1] +include::core/packages.asciidoc[leveloffset=+1] + include::security/index.asciidoc[leveloffset=+1] include::add-data-tutorials.asciidoc[leveloffset=+1] From fcf838e1f389cae6fcaeb9f7bce713123a01c939 Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Mon, 21 Aug 2023 21:02:00 +0100 Subject: [PATCH 18/29] Add threat indicator fields to prebuilt rule filterlist. (#164275) ## Summary Adds a filterlist entry for threat indicator match rules to the prebuilt rule alert filterlist. I will open an OOB artifact separately. ### 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 - [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 - [ ] 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#kibana-release-notes-process) --- .../lib/telemetry/filterlists/index.test.ts | 23 ++++++++++ .../filterlists/prebuilt_rules_alerts.ts | 46 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts index 2cfb0a4ff1f64..d63fa7510f495 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/index.test.ts @@ -255,5 +255,28 @@ describe('Security Telemetry filters', () => { 'kubernetes.pod.ip': '10-245-0-5', }); }); + + it('copies over threat indicator fields', () => { + const event = { + not_event: 'much data, much wow', + threat: { + feed: { + name: 'test_feed', + reference: 'test', + description: 'this is a test description', + dashboard_id: '69c33c01-f856-42c6-b23f-4a6e1c98fe82', + }, + }, + }; + expect(copyAllowlistedFields(prebuiltRuleAllowlistFields, event)).toStrictEqual({ + threat: { + feed: { + name: 'test_feed', + reference: 'test', + description: 'this is a test description', + }, + }, + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts index 0c74f4dd35508..98d4816e01cee 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/filterlists/prebuilt_rules_alerts.ts @@ -205,6 +205,52 @@ export const prebuiltRuleAllowlistFields: AllowlistFields = { name: true, }, source: true, + threat: { + enrichments: { + indicator: { + confidence: true, + description: true, + email: { + address: true, + }, + first_seen: true, + ip: true, + last_seen: true, + marking: { + tlp: true, + tlp_version: true, + }, + modified_at: true, + name: true, + port: true, + provider: true, + reference: true, + scanner_stats: true, + sightings: true, + type: true, + matched: { + atomic: true, + field: true, + id: true, + index: true, + occurred: true, + type: true, + }, + }, + }, + feed: { + description: true, + name: true, + reference: true, + }, + framework: true, + group: { + alias: true, + id: true, + name: true, + reference: true, + }, + }, tls: { server: { hash: true, From 43135b6a5dd88f297ec721dc2b9504a16be839e9 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Mon, 21 Aug 2023 22:25:55 +0200 Subject: [PATCH 19/29] [Security Solution] Enable Detections Coverage Overview dashboard by default (#164343) **Epic:** https://github.com/elastic/security-team/issues/2905 (internal) ## Summary Enables the Detections Coverage Overview dashboard feature flag by default. We're aiming to release this feature in 8.10. Before the last BC, we will: - remove the flag if we're confident that we should release the feature in 8.10 - otherwise, revert the flag back to `false` by default --- .../plugins/security_solution/common/experimental_features.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 8393ef508c097..7314277c440f2 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -92,11 +92,11 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables Protections/Detections Coverage Overview page (Epic link https://github.com/elastic/security-team/issues/2905) * - * This flag aims to facilitate the development process as the feature may not make it to 8.9 release. + * This flag aims to facilitate the development process as the feature may not make it to 8.10 release. * * The flag doesn't have to be documented and has to be removed after the feature is ready to release. */ - detectionsCoverageOverview: false, + detectionsCoverageOverview: true, /** * Enable risk engine client and initialisation of datastream, component templates and mappings From 823890034079c146ccc012de6f9d84aed9da3674 Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Mon, 21 Aug 2023 22:47:54 +0200 Subject: [PATCH 20/29] [Security Solution] Update CODEOWNERS for the Detection Engine team (#164359) ## Summary This PR updates the COEOWNERS file by adding missing Cypress tests folders owned by the @elastic/security-detection-engine team. --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fbc2b28e9f8a5..2add1ca0d24c5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1227,6 +1227,8 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/plugins/security_solution/server/lib/sourcerer @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/data_sources @elastic/security-detection-engine +/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts @elastic/security-detection-engine +/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_actions @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_creation @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/value_lists @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics @elastic/security-detection-engine From 8a6dfb825f8d0c8415a3f3189da02d0ca15f490b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 14:39:40 -0700 Subject: [PATCH 21/29] Update dependency @elastic/apm-rum-react to v2 (main) (#163973) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4d80cac344eb2..e24f8094b9509 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", "@elastic/apm-rum": "^5.14.0", - "@elastic/apm-rum-react": "^1.4.4", + "@elastic/apm-rum-react": "^2.0.0", "@elastic/charts": "59.1.0", "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1", diff --git a/yarn.lock b/yarn.lock index 8b4e4086a0881..f7afb86583c80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1472,10 +1472,10 @@ opentracing "^0.14.3" promise-polyfill "^8.1.3" -"@elastic/apm-rum-react@^1.4.4": - version "1.4.4" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.4.4.tgz#f716d9a5b44e2c8d89b47fb90ad24264c4e67cea" - integrity sha512-j6WZSDlA1SsWuAhn9bv2HGXFhoHe3TQVvOysUXdRvCyo2yzzdiwGQeqJs5Gl4dfxqZmyFnlutpAnoygTJVWdtQ== +"@elastic/apm-rum-react@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-2.0.0.tgz#06b39eed7cdd96cc6fdcc834c5339795717c4c68" + integrity sha512-0hegYHNvhAv3Odk+OzDEvsqqM5FZEr+YoYwp1rViayaGgkWXuwisyLGZDxZz4Huym2lVfMITEmy6HgAQxdKXRA== dependencies: "@elastic/apm-rum" "^5.14.0" hoist-non-react-statics "^3.3.0" From 05a8ce13d7bf3eb78dc16104738f234e3cbe0bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Mon, 21 Aug 2023 23:39:56 +0200 Subject: [PATCH 22/29] [APM Config] Allow API Key environment var (#163153) --- dev_docs/tutorials/debugging.mdx | 2 +- packages/kbn-apm-config-loader/src/config.test.ts | 10 ++++++++++ packages/kbn-apm-config-loader/src/config.ts | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/dev_docs/tutorials/debugging.mdx b/dev_docs/tutorials/debugging.mdx index 2f9818f571046..679945b24ea39 100644 --- a/dev_docs/tutorials/debugging.mdx +++ b/dev_docs/tutorials/debugging.mdx @@ -106,4 +106,4 @@ Set the following environment variables to enable APM: * ELASTIC_APM_ACTIVE * ELASTIC_APM_SERVER_URL - * ELASTIC_APM_SECRET_TOKEN + * ELASTIC_APM_SECRET_TOKEN or ELASTIC_APM_API_KEY diff --git a/packages/kbn-apm-config-loader/src/config.test.ts b/packages/kbn-apm-config-loader/src/config.test.ts index 697e601d92fdd..ff7afefa204cc 100644 --- a/packages/kbn-apm-config-loader/src/config.test.ts +++ b/packages/kbn-apm-config-loader/src/config.test.ts @@ -152,6 +152,7 @@ describe('ApmConfiguration', () => { beforeEach(() => { delete process.env.ELASTIC_APM_ENVIRONMENT; delete process.env.ELASTIC_APM_SECRET_TOKEN; + delete process.env.ELASTIC_APM_API_KEY; delete process.env.ELASTIC_APM_SERVER_URL; delete process.env.NODE_ENV; }); @@ -225,6 +226,15 @@ describe('ApmConfiguration', () => { expect(serverConfig).toHaveProperty('secretToken', process.env.ELASTIC_APM_SECRET_TOKEN); expect(serverConfig).toHaveProperty('serverUrl', process.env.ELASTIC_APM_SERVER_URL); }); + + it('uses apiKey instead of secret token if env var is set', () => { + process.env.ELASTIC_APM_API_KEY = 'banana'; + process.env.ELASTIC_APM_SERVER_URL = 'http://banana.com/'; + const config = new ApmConfiguration(mockedRootDir, {}, false); + const serverConfig = config.getConfig('serviceName'); + expect(serverConfig).toHaveProperty('apiKey', process.env.ELASTIC_APM_API_KEY); + expect(serverConfig).toHaveProperty('serverUrl', process.env.ELASTIC_APM_SERVER_URL); + }); }); describe('contextPropagationOnly', () => { diff --git a/packages/kbn-apm-config-loader/src/config.ts b/packages/kbn-apm-config-loader/src/config.ts index 0552329acd205..718f3e76236ca 100644 --- a/packages/kbn-apm-config-loader/src/config.ts +++ b/packages/kbn-apm-config-loader/src/config.ts @@ -147,6 +147,10 @@ export class ApmConfiguration { config.secretToken = process.env.ELASTIC_APM_SECRET_TOKEN; } + if (process.env.ELASTIC_APM_API_KEY) { + config.apiKey = process.env.ELASTIC_APM_API_KEY; + } + if (process.env.ELASTIC_APM_GLOBAL_LABELS) { config.globalLabels = Object.fromEntries( process.env.ELASTIC_APM_GLOBAL_LABELS.split(',').map((p) => { From d7bf7efcdfb46ef8d44e9b554d670e13010bfab6 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Mon, 21 Aug 2023 17:52:36 -0400 Subject: [PATCH 23/29] [RAM] Use ruletype to determine alert indices (#163574) ## Summary We were using the feature Id to determine the alert indices, but we realized that we should use the rule type id instead. Meaning that we check which rule type does the user have access and then we get the indices related to this rule type. We also took advantage of the new suggestion abstraction of the search bar components to remove the toaster of hell -> https://github.com/elastic/kibana/issues/163003 ### Checklist - [ ] [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: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../alerting_authorization.mock.ts | 1 + .../alerting_authorization.test.ts | 56 +++-- .../authorization/alerting_authorization.ts | 117 +++++++---- .../create_get_alert_indices_alias.test.ts | 112 ++++++++++ .../lib/create_get_alert_indices_alias.ts | 29 +++ x-pack/plugins/alerting/server/lib/index.ts | 2 + x-pack/plugins/alerting/server/mocks.ts | 1 + x-pack/plugins/alerting/server/plugin.ts | 4 + .../plugins/alerting/server/routes/index.ts | 18 +- .../values_suggestion_alerts.test.ts | 89 ++++++++ .../suggestions/values_suggestion_alerts.ts | 128 ++++++++++++ .../values_suggestion_rules.test.ts | 6 +- .../suggestions/values_suggestion_rules.ts | 2 +- .../alerting/server/rules_client.mock.ts | 1 + x-pack/plugins/rule_registry/server/plugin.ts | 1 - .../search_strategy/search_strategy.test.ts | 191 +++++------------- .../server/search_strategy/search_strategy.ts | 54 ++--- .../common/experimental_features.ts | 2 +- .../hooks/use_alert_data_view.test.ts | 45 +++-- .../application/hooks/use_alert_data_view.ts | 60 +++++- .../alerts_search_bar/alerts_search_bar.tsx | 6 +- .../tests/alerting/suggestions_value_alert.ts | 55 +++++ .../common/lib/authentication/roles.ts | 23 +++ .../common/lib/authentication/users.ts | 8 + .../tests/basic/search_strategy.ts | 60 ++---- 25 files changed, 766 insertions(+), 305 deletions(-) create mode 100644 x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts create mode 100644 x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.ts create mode 100644 x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts create mode 100644 x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.ts create mode 100644 x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts index 8560e9a2d9335..e51a5c9b12c79 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts @@ -16,6 +16,7 @@ const createAlertingAuthorizationMock = () => { ensureAuthorized: jest.fn(), filterByRuleTypeAuthorization: jest.fn(), getAuthorizationFilter: jest.fn(), + getAuthorizedRuleTypes: jest.fn(), getFindAuthorizationFilter: jest.fn(), getAugmentedRuleTypesWithAuthorization: jest.fn(), getSpaceId: jest.fn(), diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index 333623588aa64..83f90b68a99b1 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -52,27 +52,28 @@ function mockSecurity() { return { authorization }; } -function mockFeature(appName: string, typeName?: string) { +function mockFeature(appName: string, typeName?: string | string[]) { + const typeNameArray = typeName ? (Array.isArray(typeName) ? typeName : [typeName]) : undefined; return new KibanaFeature({ id: appName, name: appName, app: [], category: { id: 'foo', label: 'foo' }, - ...(typeName + ...(typeNameArray ? { - alerting: [typeName], + alerting: typeNameArray, } : {}), privileges: { all: { - ...(typeName + ...(typeNameArray ? { alerting: { rule: { - all: [typeName], + all: typeNameArray, }, alert: { - all: [typeName], + all: typeNameArray, }, }, } @@ -84,14 +85,14 @@ function mockFeature(appName: string, typeName?: string) { ui: [], }, read: { - ...(typeName + ...(typeNameArray ? { alerting: { rule: { - read: [typeName], + read: typeNameArray, }, alert: { - read: [typeName], + read: typeNameArray, }, }, } @@ -815,6 +816,12 @@ describe('AlertingAuthorization', () => { ensureRuleTypeIsAuthorized('someMadeUpType', 'myApp', 'rule'); }); test('creates a filter based on the privileged types', async () => { + features.getKibanaFeatures.mockReturnValue([ + mockFeature('myApp', ['myAppAlertType', 'mySecondAppAlertType']), + mockFeature('alerts', 'myOtherAppAlertType'), + myOtherAppFeature, + myAppWithSubFeature, + ]); const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType @@ -846,7 +853,7 @@ describe('AlertingAuthorization', () => { ).filter ).toEqual( fromKueryExpression( - `((path.to.rule_type_id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:myOtherAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:mySecondAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` + `((path.to.rule_type_id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:mySecondAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:myOtherAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` ) ); }); @@ -894,6 +901,10 @@ describe('AlertingAuthorization', () => { ); }); test('creates an `ensureRuleTypeIsAuthorized` function which throws if type is unauthorized', async () => { + features.getKibanaFeatures.mockReturnValue([ + mockFeature('myApp', ['myOtherAppAlertType', 'myAppAlertType']), + mockFeature('myOtherApp', ['myOtherAppAlertType', 'myAppAlertType']), + ]); const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType @@ -954,6 +965,10 @@ describe('AlertingAuthorization', () => { ); }); test('creates an `ensureRuleTypeIsAuthorized` function which is no-op if type is authorized', async () => { + features.getKibanaFeatures.mockReturnValue([ + mockFeature('myApp', ['myOtherAppAlertType', 'myAppAlertType']), + mockFeature('myOtherApp', 'myAppAlertType'), + ]); const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType @@ -1012,6 +1027,10 @@ describe('AlertingAuthorization', () => { }).not.toThrow(); }); test('creates an `logSuccessfulAuthorization` function which logs every authorized type', async () => { + features.getKibanaFeatures.mockReturnValue([ + mockFeature('myApp', ['myOtherAppAlertType', 'myAppAlertType', 'mySecondAppAlertType']), + mockFeature('myOtherApp', ['mySecondAppAlertType', 'myAppAlertType']), + ]); const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType @@ -1140,8 +1159,19 @@ describe('AlertingAuthorization', () => { enabledInLicense: true, }; const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType]); - + beforeEach(() => { + features.getKibanaFeatures.mockReturnValue([ + mockFeature('myApp', ['myOtherAppAlertType', 'myAppAlertType']), + mockFeature('myOtherApp', ['myAppAlertType', 'myOtherAppAlertType']), + ]); + }); test('augments a list of types with all features when there is no authorization api', async () => { + features.getKibanaFeatures.mockReturnValue([ + myAppFeature, + myOtherAppFeature, + myAppWithSubFeature, + myFeatureWithoutAlerting, + ]); const alertAuthorization = new AlertingAuthorization({ request, ruleTypeRegistry, @@ -1670,7 +1700,9 @@ describe('AlertingAuthorization', () => { isExportable: true, }; const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType, mySecondAppAlertType]); - + beforeEach(() => { + features.getKibanaFeatures.mockReturnValue([mockFeature('myApp', ['myOtherAppAlertType'])]); + }); test('it returns authorized rule types given a set of feature ids', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 7264898c10648..4eb901173bc89 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -92,7 +92,7 @@ export class AlertingAuthorization { private readonly featuresIds: Promise>; private readonly allPossibleConsumers: Promise; private readonly spaceId: string | undefined; - + private readonly features: FeaturesPluginStart; constructor({ ruleTypeRegistry, request, @@ -104,7 +104,7 @@ export class AlertingAuthorization { this.request = request; this.authorization = authorization; this.ruleTypeRegistry = ruleTypeRegistry; - + this.features = features; this.spaceId = getSpaceId(request); this.featuresIds = getSpace(request) @@ -262,6 +262,19 @@ export class AlertingAuthorization { return this.getAuthorizationFilter(authorizationEntity, filterOpts, ReadOperations.Find); } + public async getAuthorizedRuleTypes( + authorizationEntity: AlertingAuthorizationEntity, + featuresIds?: Set + ): Promise { + const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization( + this.ruleTypeRegistry.list(), + [ReadOperations.Find], + authorizationEntity, + featuresIds + ); + return Array.from(authorizedRuleTypes); + } + public async getAuthorizationFilter( authorizationEntity: AlertingAuthorizationEntity, filterOpts: AlertingAuthorizationFilterOpts, @@ -355,28 +368,44 @@ export class AlertingAuthorization { ); // add an empty `authorizedConsumers` array on each ruleType - const ruleTypesWithAuthorization = this.augmentWithAuthorizedConsumers(ruleTypes, {}); - + const ruleTypesWithAuthorization = Array.from( + this.augmentWithAuthorizedConsumers(ruleTypes, {}) + ); + const ruleTypesAuthorized: Map = new Map(); // map from privilege to ruleType which we can refer back to when analyzing the result // of checkPrivileges const privilegeToRuleType = new Map< string, [RegistryAlertTypeWithAuth, string, HasPrivileges, IsAuthorizedAtProducerLevel] >(); - // as we can't ask ES for the user's individual privileges we need to ask for each feature - // and ruleType in the system whether this user has this privilege - for (const ruleType of ruleTypesWithAuthorization) { - for (const feature of fIds) { - for (const operation of operations) { - privilegeToRuleType.set( - this.authorization!.actions.alerting.get( - ruleType.id, - feature, - authorizationEntity, - operation - ), - [ruleType, feature, hasPrivilegeByOperation(operation), ruleType.producer === feature] - ); + for (const feature of fIds) { + const featureDef = this.features + .getKibanaFeatures() + .find((kFeature) => kFeature.id === feature); + for (const ruleTypeId of featureDef?.alerting ?? []) { + const ruleTypeAuth = ruleTypesWithAuthorization.find((rtwa) => rtwa.id === ruleTypeId); + if (ruleTypeAuth) { + if (!ruleTypesAuthorized.has(ruleTypeId)) { + const { authorizedConsumers, hasAlertsMappings, hasFieldsForAAD, ...ruleType } = + ruleTypeAuth; + ruleTypesAuthorized.set(ruleTypeId, ruleType); + } + for (const operation of operations) { + privilegeToRuleType.set( + this.authorization!.actions.alerting.get( + ruleTypeId, + feature, + authorizationEntity, + operation + ), + [ + ruleTypeAuth, + feature, + hasPrivilegeByOperation(operation), + ruleTypeAuth.producer === feature, + ] + ); + } } } } @@ -388,30 +417,36 @@ export class AlertingAuthorization { return { username, hasAllRequested, - authorizedRuleTypes: hasAllRequested - ? // has access to all features - this.augmentWithAuthorizedConsumers(ruleTypes, await this.allPossibleConsumers) - : // only has some of the required privileges - privileges.kibana.reduce((authorizedRuleTypes, { authorized, privilege }) => { - if (authorized && privilegeToRuleType.has(privilege)) { - const [ruleType, feature, hasPrivileges, isAuthorizedAtProducerLevel] = - privilegeToRuleType.get(privilege)!; - ruleType.authorizedConsumers[feature] = mergeHasPrivileges( - hasPrivileges, - ruleType.authorizedConsumers[feature] - ); - - if (isAuthorizedAtProducerLevel) { - // granting privileges under the producer automatically authorized the Rules Management UI as well - ruleType.authorizedConsumers[ALERTS_FEATURE_ID] = mergeHasPrivileges( - hasPrivileges, - ruleType.authorizedConsumers[ALERTS_FEATURE_ID] - ); + authorizedRuleTypes: + hasAllRequested && featuresIds === undefined + ? // has access to all features + this.augmentWithAuthorizedConsumers( + new Set(ruleTypesAuthorized.values()), + await this.allPossibleConsumers + ) + : // only has some of the required privileges + privileges.kibana.reduce((authorizedRuleTypes, { authorized, privilege }) => { + if (authorized && privilegeToRuleType.has(privilege)) { + const [ruleType, feature, hasPrivileges, isAuthorizedAtProducerLevel] = + privilegeToRuleType.get(privilege)!; + if (fIds.has(feature)) { + ruleType.authorizedConsumers[feature] = mergeHasPrivileges( + hasPrivileges, + ruleType.authorizedConsumers[feature] + ); + + if (isAuthorizedAtProducerLevel) { + // granting privileges under the producer automatically authorized the Rules Management UI as well + ruleType.authorizedConsumers[ALERTS_FEATURE_ID] = mergeHasPrivileges( + hasPrivileges, + ruleType.authorizedConsumers[ALERTS_FEATURE_ID] + ); + } + authorizedRuleTypes.add(ruleType); + } } - authorizedRuleTypes.add(ruleType); - } - return authorizedRuleTypes; - }, new Set()), + return authorizedRuleTypes; + }, new Set()), }; } else { return { diff --git a/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts new file mode 100644 index 0000000000000..092acf1049eca --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts @@ -0,0 +1,112 @@ +/* + * 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 { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock'; +import { ConstructorOptions, RuleTypeRegistry } from '../rule_type_registry'; +import { TaskRunnerFactory } from '../task_runner/task_runner_factory'; +import { ILicenseState } from './license_state'; +import { licenseStateMock } from './license_state.mock'; +import { schema } from '@kbn/config-schema'; +import { createGetAlertIndicesAliasFn } from './create_get_alert_indices_alias'; + +describe('createGetAlertIndicesAliasFn', () => { + const logger = loggingSystemMock.create().get(); + const mockedLicenseState: jest.Mocked = licenseStateMock.create(); + const taskManager = taskManagerMock.createSetup(); + const inMemoryMetrics = inMemoryMetricsMock.create(); + + const ruleTypeRegistryParams: ConstructorOptions = { + logger, + taskManager, + taskRunnerFactory: new TaskRunnerFactory(), + alertsService: null, + licenseState: mockedLicenseState, + licensing: licensingMock.createSetup(), + minimumScheduleInterval: { value: '1m', enforce: false }, + inMemoryMetrics, + }; + const registry = new RuleTypeRegistry(ruleTypeRegistryParams); + registry.register({ + id: 'test', + name: 'Test', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + producer: 'alerts', + alerts: { + context: 'test', + mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, + }, + validate: { + params: { validate: (params) => params }, + }, + }); + registry.register({ + id: 'spaceAware', + name: 'Space Aware', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + producer: 'alerts', + alerts: { + context: 'spaceAware', + isSpaceAware: true, + mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, + }, + validate: { + params: { validate: (params) => params }, + }, + }); + registry.register({ + id: 'foo', + name: 'Foo', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + producer: 'alerts', + validate: { + params: schema.any(), + }, + }); + const getAlertIndicesAlias = createGetAlertIndicesAliasFn(registry); + + test('getAlertIndicesAlias for the rule type with alert context', () => { + expect(getAlertIndicesAlias(['test'])).toEqual(['.alerts-test.alerts-default']); + }); + test('getAlertIndicesAlias for the rule type with alert context with space I', () => { + expect(getAlertIndicesAlias(['spaceAware'], 'space-1')).toEqual([ + '.alerts-spaceAware.alerts-space-1', + ]); + }); + test('getAlertIndicesAlias for the rule type with NO alert context', () => { + expect(getAlertIndicesAlias(['foo'])).toEqual([]); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.ts b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.ts new file mode 100644 index 0000000000000..f6c08c4793fb2 --- /dev/null +++ b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; +import { getIndexTemplateAndPattern } from '../alerts_service/resource_installer_utils'; +import { RuleTypeRegistry } from '../rule_type_registry'; + +export type GetAlertIndicesAlias = (rulesTypes: string[], spaceId?: string) => string[]; + +export function createGetAlertIndicesAliasFn(ruleTypeRegistry: RuleTypeRegistry) { + return (rulesTypes: string[], spaceId?: string): string[] => { + const aliases = new Set(); + rulesTypes.forEach((ruleTypeId) => { + const ruleType = ruleTypeRegistry.get(ruleTypeId); + if (ruleType.alerts?.context) { + const indexTemplateAndPattern = getIndexTemplateAndPattern({ + context: ruleType.alerts?.context, + namespace: ruleType.alerts?.isSpaceAware && spaceId ? spaceId : DEFAULT_NAMESPACE_STRING, + }); + aliases.add(indexTemplateAndPattern.alias); + } + }); + return Array.from(aliases); + }; +} diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerting/server/lib/index.ts index db28ae5ff5649..4257f3ca916a4 100644 --- a/x-pack/plugins/alerting/server/lib/index.ts +++ b/x-pack/plugins/alerting/server/lib/index.ts @@ -46,3 +46,5 @@ export { determineAlertsToReturn } from './determine_alerts_to_return'; export { updateFlappingHistory, isFlapping } from './flapping_utils'; export { getAlertsForNotification } from './get_alerts_for_notification'; export { trimRecoveredAlerts } from './trim_recovered_alerts'; +export { createGetAlertIndicesAliasFn } from './create_get_alert_indices_alias'; +export type { GetAlertIndicesAlias } from './create_get_alert_indices_alias'; diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index a4902fccb4f04..9aa7209ea5fa1 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -59,6 +59,7 @@ const createStartMock = () => { listTypes: jest.fn(), getType: jest.fn(), getAllTypes: jest.fn(), + getAlertIndicesAlias: jest.fn(), getAlertingAuthorizationWithRequest: jest.fn(), getRulesClientWithRequest: jest.fn().mockResolvedValue(rulesClientMock.create()), getFrameworkHealth: jest.fn(), diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 9e81e1f53e87d..dd20272b0fb68 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -97,6 +97,7 @@ import { } from './alerts_service'; import { rulesSettingsFeature } from './rules_settings_feature'; import { maintenanceWindowFeature } from './maintenance_window_feature'; +import { createGetAlertIndicesAliasFn, GetAlertIndicesAlias } from './lib'; export const EVENT_LOG_PROVIDER = 'alerting'; export const EVENT_LOG_ACTIONS = { @@ -145,6 +146,7 @@ export interface PluginStartContract { getAllTypes: RuleTypeRegistry['getAllTypes']; getType: RuleTypeRegistry['get']; + getAlertIndicesAlias: GetAlertIndicesAlias; getRulesClientWithRequest(request: KibanaRequest): RulesClientApi; @@ -345,6 +347,7 @@ export class AlertingPlugin { router, licenseState: this.licenseState, usageCounter: this.usageCounter, + getAlertIndicesAlias: createGetAlertIndicesAliasFn(this.ruleTypeRegistry!), encryptedSavedObjects: plugins.encryptedSavedObjects, config$: plugins.unifiedSearch.autocomplete.getInitializerContextConfig().create(), }); @@ -556,6 +559,7 @@ export class AlertingPlugin { listTypes: ruleTypeRegistry!.list.bind(this.ruleTypeRegistry!), getType: ruleTypeRegistry!.get.bind(this.ruleTypeRegistry), getAllTypes: ruleTypeRegistry!.getAllTypes.bind(this.ruleTypeRegistry!), + getAlertIndicesAlias: createGetAlertIndicesAliasFn(this.ruleTypeRegistry!), getAlertingAuthorizationWithRequest, getRulesClientWithRequest, getFrameworkHealth: async () => diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index f9962d72eb772..7b9acfc7f77df 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -10,7 +10,7 @@ import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import type { ConfigSchema } from '@kbn/unified-search-plugin/config'; import { Observable } from 'rxjs'; -import { ILicenseState } from '../lib'; +import { GetAlertIndicesAlias, ILicenseState } from '../lib'; import { defineLegacyRoutes } from './legacy'; import { AlertingRequestHandlerContext } from '../types'; import { createRuleRoute } from './rule/apis/create'; @@ -56,20 +56,29 @@ import { findMaintenanceWindowsRoute } from './maintenance_window/find_maintenan import { archiveMaintenanceWindowRoute } from './maintenance_window/archive_maintenance_window'; import { finishMaintenanceWindowRoute } from './maintenance_window/finish_maintenance_window'; import { activeMaintenanceWindowsRoute } from './maintenance_window/active_maintenance_windows'; -import { registerValueSuggestionsRoute } from './suggestions/values_suggestion_rules'; +import { registerRulesValueSuggestionsRoute } from './suggestions/values_suggestion_rules'; import { registerFieldsRoute } from './suggestions/fields_rules'; import { bulkGetMaintenanceWindowRoute } from './maintenance_window/bulk_get_maintenance_windows'; +import { registerAlertsValueSuggestionsRoute } from './suggestions/values_suggestion_alerts'; export interface RouteOptions { router: IRouter; licenseState: ILicenseState; encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; + getAlertIndicesAlias?: GetAlertIndicesAlias; usageCounter?: UsageCounter; config$?: Observable; } export function defineRoutes(opts: RouteOptions) { - const { router, licenseState, encryptedSavedObjects, usageCounter, config$ } = opts; + const { + router, + licenseState, + encryptedSavedObjects, + usageCounter, + config$, + getAlertIndicesAlias, + } = opts; defineLegacyRoutes(opts); createRuleRoute(opts); @@ -116,7 +125,8 @@ export function defineRoutes(opts: RouteOptions) { archiveMaintenanceWindowRoute(router, licenseState); finishMaintenanceWindowRoute(router, licenseState); activeMaintenanceWindowsRoute(router, licenseState); - registerValueSuggestionsRoute(router, licenseState, config$!); + registerAlertsValueSuggestionsRoute(router, licenseState, config$!, getAlertIndicesAlias); + registerRulesValueSuggestionsRoute(router, licenseState, config$!); registerFieldsRoute(router, licenseState); bulkGetMaintenanceWindowRoute(router, licenseState); } diff --git a/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts new file mode 100644 index 0000000000000..fd6c014664121 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks'; +import type { ConfigSchema } from '@kbn/unified-search-plugin/config'; +import { dataPluginMock } from '@kbn/unified-search-plugin/server/mocks'; +import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; +import { Observable } from 'rxjs'; +import { licenseStateMock } from '../../lib/license_state.mock'; +import { rulesClientMock } from '../../rules_client.mock'; +import { mockHandlerArguments } from '../_mock_handler_arguments'; +import { registerAlertsValueSuggestionsRoute } from './values_suggestion_alerts'; + +jest.mock('@kbn/unified-search-plugin/server/autocomplete/terms_agg', () => { + return { + termsAggSuggestions: jest.fn(), + }; +}); + +const termsAggSuggestionsMock = termsAggSuggestions as jest.Mock; + +jest.mock('../../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +describe('registerAlertsValueSuggestionsRoute', () => { + const rulesClient = rulesClientMock.create(); + let config$: Observable; + + beforeEach(() => { + termsAggSuggestionsMock.mockClear(); + rulesClient.getSpaceId.mockReturnValue('space-x'); + config$ = dataPluginMock + .createSetupContract() + .autocomplete.getInitializerContextConfig() + .create(); + }); + + test('happy path route registered', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + const getAlertIndicesAliasMock = jest.fn().mockReturnValue(['alert-index']); + registerAlertsValueSuggestionsRoute(router, licenseState, config$, getAlertIndicesAliasMock); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerts/suggestions/values"`); + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + field: 'alert.tags', + query: 'test-query', + filters: 'test-filters', + fieldMeta: 'test-field-meta', + }, + }); + + const [context, req, res] = mockHandlerArguments({ rulesClient }, mockRequest, ['ok']); + + await handler( + { + ...context, + core: { elasticsearch: { client: { asInternalUser: {} } }, savedObjects: { client: {} } }, + }, + req, + res + ); + + expect(rulesClient.getAuthorization).toHaveBeenCalledTimes(1); + expect(rulesClient.getSpaceId).toHaveBeenCalledTimes(1); + expect(termsAggSuggestionsMock).toHaveBeenNthCalledWith( + 1, + expect.any(Object), + expect.any(Object), + expect.any(Object), + 'alert-index', + 'alert.tags', + 'test-query', + [{ term: { 'kibana.space_ids': 'space-x' } }], + 'test-field-meta', + expect.any(Object) + ); + expect(res.ok).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.ts b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.ts new file mode 100644 index 0000000000000..47a603014a119 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_alerts.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from '@kbn/core/server'; +import { firstValueFrom, Observable } from 'rxjs'; +import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; +import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; +import type { ConfigSchema } from '@kbn/unified-search-plugin/config'; +import { UsageCounter } from '@kbn/usage-collection-plugin/server'; +import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + AlertConsumers, + ALERT_RULE_CONSUMER, + ALERT_RULE_TYPE_ID, + SPACE_IDS, +} from '@kbn/rule-data-utils'; + +import { verifyAccessAndContext } from '../lib'; +import { RuleAuditAction, ruleAuditEvent } from '../../rules_client/common/audit_events'; +import { + AlertingAuthorizationEntity, + AlertingAuthorizationFilterOpts, + AlertingAuthorizationFilterType, +} from '../../authorization'; +import { AlertingRequestHandlerContext } from '../../types'; +import { GetAlertIndicesAlias, ILicenseState } from '../../lib'; + +const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = { + type: AlertingAuthorizationFilterType.ESDSL, + fieldNames: { ruleTypeId: ALERT_RULE_TYPE_ID, consumer: ALERT_RULE_CONSUMER }, +}; + +export const AlertsSuggestionsSchema = { + body: schema.object({ + field: schema.string(), + query: schema.string(), + filters: schema.maybe(schema.any()), + fieldMeta: schema.maybe(schema.any()), + }), +}; + +const VALID_FEATURE_IDS = new Set([ + AlertConsumers.APM, + AlertConsumers.INFRASTRUCTURE, + AlertConsumers.LOGS, + AlertConsumers.SLO, + AlertConsumers.UPTIME, +]); + +export function registerAlertsValueSuggestionsRoute( + router: IRouter, + licenseState: ILicenseState, + config$: Observable, + getAlertIndicesAlias?: GetAlertIndicesAlias, + usageCounter?: UsageCounter +) { + router.post( + { + path: '/internal/alerts/suggestions/values', + validate: AlertsSuggestionsSchema, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, request, response) { + const config = await firstValueFrom(config$); + const { field: fieldName, query, fieldMeta } = request.body; + const abortSignal = getRequestAbortedSignal(request.events.aborted$); + const { savedObjects, elasticsearch } = await context.core; + + const rulesClient = (await context.alerting).getRulesClient(); + let authorizationTuple; + let authorizedRuleType = []; + try { + const authorization = rulesClient.getAuthorization(); + authorizationTuple = await authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Alert, + alertingAuthorizationFilterOpts + ); + authorizedRuleType = await authorization.getAuthorizedRuleTypes( + AlertingAuthorizationEntity.Alert, + VALID_FEATURE_IDS + ); + } catch (error) { + rulesClient.getAuditLogger()?.log( + ruleAuditEvent({ + action: RuleAuditAction.FIND, + error, + }) + ); + throw error; + } + const spaceId = rulesClient.getSpaceId(); + const { filter: authorizationFilter } = authorizationTuple; + const filters = [ + ...(authorizationFilter != null ? [authorizationFilter] : []), + { term: { [SPACE_IDS]: spaceId } }, + ] as estypes.QueryDslQueryContainer[]; + + const index = getAlertIndicesAlias!( + authorizedRuleType.map((art) => art.id), + spaceId + ).join(','); + try { + const body = await termsAggSuggestions( + config, + savedObjects.client, + elasticsearch.client.asInternalUser, + index, + fieldName, + query, + filters, + fieldMeta, + abortSignal + ); + return response.ok({ body }); + } catch (e) { + const kbnErr = getKbnServerError(e); + return reportServerError(response, kbnErr); + } + }) + ) + ); +} diff --git a/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.test.ts b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.test.ts index 0408148b599de..e29f15bafa08a 100644 --- a/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.test.ts +++ b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.test.ts @@ -13,7 +13,7 @@ import { Observable } from 'rxjs'; import { licenseStateMock } from '../../lib/license_state.mock'; import { rulesClientMock } from '../../rules_client.mock'; import { mockHandlerArguments } from '../_mock_handler_arguments'; -import { registerValueSuggestionsRoute } from './values_suggestion_rules'; +import { registerRulesValueSuggestionsRoute } from './values_suggestion_rules'; jest.mock('@kbn/unified-search-plugin/server/autocomplete/terms_agg', () => { return { @@ -27,7 +27,7 @@ jest.mock('../../lib/license_api_access', () => ({ verifyApiAccess: jest.fn(), })); -describe('registerValueSuggestionsRoute', () => { +describe('registerRulesValueSuggestionsRoute', () => { const rulesClient = rulesClientMock.create(); let config$: Observable; @@ -43,7 +43,7 @@ describe('registerValueSuggestionsRoute', () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - registerValueSuggestionsRoute(router, licenseState, config$); + registerRulesValueSuggestionsRoute(router, licenseState, config$); const [config, handler] = router.post.mock.calls[0]; diff --git a/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.ts b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.ts index 4a36fb0241f86..6c34efb214c34 100644 --- a/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.ts +++ b/x-pack/plugins/alerting/server/routes/suggestions/values_suggestion_rules.ts @@ -40,7 +40,7 @@ export const RulesSuggestionsSchema = { }), }; -export function registerValueSuggestionsRoute( +export function registerRulesValueSuggestionsRoute( router: IRouter, licenseState: ILicenseState, config$: Observable, diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 1c3cfcc5238f6..4b98ef3abaf9d 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -33,6 +33,7 @@ const createRulesClientMock = () => { getAuditLogger: jest.fn(), getAuthorization: jest.fn().mockImplementation(() => ({ getFindAuthorizationFilter: jest.fn().mockReturnValue({ filter: null }), + getAuthorizedRuleTypes: jest.fn().mockResolvedValue([]), })), getExecutionLogForRule: jest.fn(), getRuleExecutionKPI: jest.fn(), diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 42ebef4c41f6f..c561052669fdd 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -119,7 +119,6 @@ export class RuleRegistryPlugin core.getStartServices().then(([_, depsStart]) => { const ruleRegistrySearchStrategy = ruleRegistrySearchStrategyProvider( depsStart.data, - this.ruleDataService!, depsStart.alerting, logger, plugins.security, diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts index c8d1114ce653c..463f70479dd57 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.test.ts @@ -9,14 +9,12 @@ import { merge } from 'lodash'; import { loggerMock } from '@kbn/logging-mocks'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { ruleRegistrySearchStrategyProvider, EMPTY_RESPONSE } from './search_strategy'; -import { ruleDataServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { SearchStrategyDependencies } from '@kbn/data-plugin/server'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { spacesMock } from '@kbn/spaces-plugin/server/mocks'; import { RuleRegistrySearchRequest } from '../../common/search_strategy'; -import { IndexInfo } from '../rule_data_plugin_service/index_info'; import * as getAuthzFilterImport from '../lib/get_authz_filter'; import { getIsKibanaRequest } from '../lib/get_is_kibana_request'; @@ -50,12 +48,12 @@ const getBasicResponse = (overwrites = {}) => { describe('ruleRegistrySearchStrategyProvider()', () => { const data = dataPluginMock.createStartContract(); - const ruleDataService = ruleDataServiceMock.create(); const alerting = alertsMock.createStart(); const security = securityMock.createSetup(); const spaces = spacesMock.createStart(); const logger = loggerMock.create(); - + const getAuthorizedRuleTypesMock = jest.fn(); + const getAlertIndicesAliasMock = jest.fn(); const response = getBasicResponse({ rawResponse: { hits: { @@ -74,11 +72,13 @@ describe('ruleRegistrySearchStrategyProvider()', () => { const searchStrategySearch = jest.fn().mockImplementation(() => of(response)); beforeEach(() => { - ruleDataService.findIndexByFeature.mockImplementation(() => { - return { - baseName: 'test', - } as IndexInfo; - }); + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['test']); + const authorizationMock = { + getAuthorizedRuleTypes: getAuthorizedRuleTypesMock, + } as never; + alerting.getAlertingAuthorizationWithRequest.mockResolvedValue(authorizationMock); + alerting.getAlertIndicesAlias = getAlertIndicesAliasMock; data.search.getSearchStrategy.mockImplementation(() => { return { @@ -102,7 +102,8 @@ describe('ruleRegistrySearchStrategyProvider()', () => { }); afterEach(() => { - ruleDataService.findIndexByFeature.mockClear(); + getAuthorizedRuleTypesMock.mockClear(); + getAlertIndicesAliasMock.mockClear(); data.search.getSearchStrategy.mockClear(); (data.search.searchAsInternalUser.search as jest.Mock).mockClear(); getAuthzFilterSpy.mockClear(); @@ -110,6 +111,8 @@ describe('ruleRegistrySearchStrategyProvider()', () => { }); it('should handle a basic search request', async () => { + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['observability-logs']); const request: RuleRegistrySearchRequest = { featureIds: [AlertConsumers.LOGS], }; @@ -118,68 +121,13 @@ describe('ruleRegistrySearchStrategyProvider()', () => { request: {}, }; - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); const result = await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) .toPromise(); - expect(result).toBe(response); - }); - - it('should use the active space in siem queries', async () => { - const request: RuleRegistrySearchRequest = { - featureIds: [AlertConsumers.SIEM], - }; - const options = {}; - const deps = { - request: {}, - }; - - spaces.spacesService.getActiveSpace.mockImplementation(async () => { - return { - id: 'testSpace', - name: 'Test Space', - disabledFeatures: [], - }; - }); - - ruleDataService.findIndexByFeature.mockImplementation(() => { - return { - baseName: 'myTestIndex', - } as unknown as IndexInfo; - }); - - let searchRequest: RuleRegistrySearchRequest = {} as unknown as RuleRegistrySearchRequest; - data.search.getSearchStrategy.mockImplementation(() => { - return { - search: (_request) => { - searchRequest = _request as unknown as RuleRegistrySearchRequest; - return of(response); - }, - }; - }); - - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); - await strategy - .search(request, options, deps as unknown as SearchStrategyDependencies) - .toPromise(); - spaces.spacesService.getActiveSpace.mockClear(); - expect(searchRequest?.params?.index).toStrictEqual(['myTestIndex-testSpace*']); + expect(result).toEqual(response); }); it('should return an empty response if no valid indices are found', async () => { @@ -191,18 +139,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { request: {}, }; - ruleDataService.findIndexByFeature.mockImplementationOnce(() => { - return null; - }); + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue([]); - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); const result = await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -219,14 +159,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { request: {}, }; - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem']); + + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -243,14 +179,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { request: {}, }; - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem', 'o11y-logs']); + + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); let err; try { @@ -271,15 +203,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { const deps = { request: {}, }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['o11y-logs']); - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -297,14 +224,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { request: {}, }; - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem']); + + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -325,15 +248,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { const deps = { request: {}, }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['o11y-logs']); - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -362,15 +280,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { const deps = { request: {}, }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['o11y-logs']); - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -392,15 +305,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { const deps = { request: {}, }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem']); - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -427,7 +335,7 @@ describe('ruleRegistrySearchStrategyProvider()', () => { sort: [], }, ignore_unavailable: true, - index: ['test-testSpace*'], + index: ['security-siem'], }, }, {}, @@ -447,15 +355,10 @@ describe('ruleRegistrySearchStrategyProvider()', () => { const deps = { request: {}, }; + getAuthorizedRuleTypesMock.mockResolvedValue([]); + getAlertIndicesAliasMock.mockReturnValue(['security-siem']); - const strategy = ruleRegistrySearchStrategyProvider( - data, - ruleDataService, - alerting, - logger, - security, - spaces - ); + const strategy = ruleRegistrySearchStrategyProvider(data, alerting, logger, security, spaces); await strategy .search(request, options, deps as unknown as SearchStrategyDependencies) @@ -477,7 +380,7 @@ describe('ruleRegistrySearchStrategyProvider()', () => { sort: [], }, ignore_unavailable: true, - index: ['test-testSpace*'], + index: ['security-siem'], }, }, {}, diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts index e7bead0cae398..3793fa83b3679 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts @@ -12,15 +12,17 @@ import { isEmpty } from 'lodash'; import { isValidFeatureId, AlertConsumers } from '@kbn/rule-data-utils'; import { ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import { ISearchStrategy, PluginStart } from '@kbn/data-plugin/server'; -import { ReadOperations, PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/server'; +import { + ReadOperations, + PluginStartContract as AlertingStart, + AlertingAuthorizationEntity, +} from '@kbn/alerting-plugin/server'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; import { RuleRegistrySearchRequest, RuleRegistrySearchResponse, } from '../../common/search_strategy'; -import { IRuleDataService } from '..'; -import { Dataset } from '../rule_data_plugin_service/index_options'; import { MAX_ALERT_SEARCH_SIZE } from '../../common/constants'; import { AlertAuditAction, alertAuditEvent } from '..'; import { getSpacesFilter, getAuthzFilter } from '../lib'; @@ -35,7 +37,6 @@ export const RULE_SEARCH_STRATEGY_NAME = 'privateRuleRegistryAlertsSearchStrateg export const ruleRegistrySearchStrategyProvider = ( data: PluginStart, - ruleDataService: IRuleDataService, alerting: AlertingStart, logger: Logger, security?: SecurityPluginSetup, @@ -57,10 +58,17 @@ export const ruleRegistrySearchStrategyProvider = ( `The ${RULE_SEARCH_STRATEGY_NAME} search strategy is unable to accommodate requests containing multiple feature IDs and one of those IDs is SIEM.` ); } + request.featureIds.forEach((featureId) => { + if (!isValidFeatureId(featureId)) { + logger.warn( + `Found invalid feature '${featureId}' while using ${RULE_SEARCH_STRATEGY_NAME} search strategy. No alert data from this feature will be searched.` + ); + } + }); const securityAuditLogger = security?.audit.asScoped(deps.request); const getActiveSpace = async () => spaces?.spacesService.getActiveSpace(deps.request); - const getAsync = async () => { + const getAsync = async (featureIds: string[]) => { const [space, authorization] = await Promise.all([ getActiveSpace(), alerting.getAlertingAuthorizationWithRequest(deps.request), @@ -73,28 +81,22 @@ export const ruleRegistrySearchStrategyProvider = ( ReadOperations.Find )) as estypes.QueryDslQueryContainer; } - return { space, authzFilter }; - }; - return from(getAsync()).pipe( - mergeMap(({ space, authzFilter }) => { - const indices: string[] = request.featureIds.reduce((accum: string[], featureId) => { - if (!isValidFeatureId(featureId)) { - logger.warn( - `Found invalid feature '${featureId}' while using ${RULE_SEARCH_STRATEGY_NAME} search strategy. No alert data from this feature will be searched.` - ); - return accum; - } - const alertIndexInfo = ruleDataService.findIndexByFeature(featureId, Dataset.alerts); - if (alertIndexInfo) { - accum.push( - featureId === 'siem' - ? `${alertIndexInfo.baseName}-${space?.id ?? ''}*` - : `${alertIndexInfo.baseName}*` - ); - } - return accum; - }, []); + const authorizedRuleTypes = + featureIds.length > 0 + ? await authorization.getAuthorizedRuleTypes( + AlertingAuthorizationEntity.Alert, + new Set(featureIds) + ) + : []; + return { space, authzFilter, authorizedRuleTypes }; + }; + return from(getAsync(request.featureIds)).pipe( + mergeMap(({ space, authzFilter, authorizedRuleTypes }) => { + const indices = alerting.getAlertIndicesAlias( + authorizedRuleTypes.map((art: { id: any }) => art.id), + space?.id + ); if (indices.length === 0) { return of(EMPTY_RESPONSE); } diff --git a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts index 66e87e0beaeec..4b59e95b300ac 100644 --- a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts +++ b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts @@ -18,7 +18,7 @@ export const allowedExperimentalValues = Object.freeze({ ruleStatusFilter: true, rulesDetailLogs: true, ruleUseExecutionStatus: false, - ruleKqlBar: true, + ruleKqlBar: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.ts index 05696e19c618a..ef1bdee1d5490 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { DataView } from '@kbn/data-views-plugin/common'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; import { act, renderHook } from '@testing-library/react-hooks'; -import { AsyncState } from 'react-use/lib/useAsync'; -import { useAlertDataView } from './use_alert_data_view'; +import { useAlertDataView, UserAlertDataView } from './use_alert_data_view'; const mockUseKibanaReturnValue = createStartServicesMock(); @@ -23,7 +21,6 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ })); describe('useAlertDataView', () => { - const mockedDataView = 'dataView'; const observabilityAlertFeatureIds: ValidFeatureId[] = [ AlertConsumers.APM, AlertConsumers.INFRASTRUCTURE, @@ -40,7 +37,6 @@ describe('useAlertDataView', () => { '.alerts-observability.apm.alerts-*', ], }); - mockUseKibanaReturnValue.data.dataViews.create = jest.fn().mockReturnValue(mockedDataView); }); afterEach(() => { @@ -51,9 +47,10 @@ describe('useAlertDataView', () => { await act(async () => { const mockedAsyncDataView = { loading: true, + error: undefined, }; - const { result, waitForNextUpdate } = renderHook>(() => + const { result, waitForNextUpdate } = renderHook(() => useAlertDataView(observabilityAlertFeatureIds) ); @@ -65,19 +62,26 @@ describe('useAlertDataView', () => { it('returns dataView for the provided featureIds', async () => { await act(async () => { - const mockedAsyncDataView = { - loading: false, - value: mockedDataView, - }; - - const { result, waitForNextUpdate } = renderHook>(() => + const { result, waitForNextUpdate } = renderHook(() => useAlertDataView(observabilityAlertFeatureIds) ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual(mockedAsyncDataView); + expect(result.current).toMatchInlineSnapshot(` + Object { + "error": undefined, + "loading": false, + "value": Array [ + Object { + "fieldFormatMap": Object {}, + "fields": Array [], + "title": ".alerts-observability.uptime.alerts-*,.alerts-observability.metrics.alerts-*,.alerts-observability.logs.alerts-*,.alerts-observability.apm.alerts-*", + }, + ], + } + `); }); }); @@ -88,19 +92,20 @@ describe('useAlertDataView', () => { }); await act(async () => { - const mockedAsyncDataView = { - loading: false, - error, - }; - - const { result, waitForNextUpdate } = renderHook>(() => + const { result, waitForNextUpdate } = renderHook(() => useAlertDataView(observabilityAlertFeatureIds) ); await waitForNextUpdate(); await waitForNextUpdate(); - expect(result.current).toEqual(mockedAsyncDataView); + expect(result.current).toMatchInlineSnapshot(` + Object { + "error": [Error: http error], + "loading": false, + "value": undefined, + } + `); }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.ts index c72ed500d42eb..15608192e7ddc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.ts @@ -5,28 +5,72 @@ * 2.0. */ -import { DataView } from '@kbn/data-views-plugin/common'; +import { DataView, FieldSpec } from '@kbn/data-views-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; import useAsync from 'react-use/lib/useAsync'; -import type { AsyncState } from 'react-use/lib/useAsync'; +import { useMemo } from 'react'; import { TriggersAndActionsUiServices } from '../..'; -export function useAlertDataView(featureIds: ValidFeatureId[]): AsyncState { - const { http, data: dataService } = useKibana().services; +export interface UserAlertDataView { + value?: DataView[]; + loading: boolean; + error?: Error; +} + +export function useAlertDataView(featureIds: ValidFeatureId[]): UserAlertDataView { + const { http } = useKibana().services; const features = featureIds.sort().join(','); - const dataView = useAsync(async () => { - const { index_name: indexNames } = await http.get<{ index_name: string[] }>( + const indexNames = useAsync(async () => { + const { index_name: indexNamesStr } = await http.get<{ index_name: string[] }>( `${BASE_RAC_ALERTS_API_PATH}/index`, { query: { features }, } ); - return dataService.dataViews.create({ title: indexNames.join(','), allowNoIndex: true }); + return indexNamesStr; + }, [features]); + + const fields = useAsync(async () => { + const { fields: alertFields } = await http.get<{ fields: FieldSpec[] }>( + `${BASE_RAC_ALERTS_API_PATH}/browser_fields`, + { + query: { featureIds }, + } + ); + return alertFields; }, [features]); - return dataView; + const dataview = useMemo( + () => + !fields.loading && + !indexNames.loading && + fields.error === undefined && + indexNames.error === undefined + ? ([ + { + title: (indexNames.value ?? []).join(','), + fieldFormatMap: {}, + fields: (fields.value ?? [])?.map((field) => { + return { + ...field, + ...(field.esTypes && field.esTypes.includes('flattened') + ? { type: 'string' } + : {}), + }; + }), + }, + ] as unknown as DataView[]) + : undefined, + [fields, indexNames] + ); + + return { + value: dataview, + loading: fields.loading || indexNames.loading, + error: fields.error ? fields.error : indexNames.error, + }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx index bded0038cfc64..5e5ced6d580eb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useState } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { Query, TimeRange } from '@kbn/es-query'; +import { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; import { NO_INDEX_PATTERNS } from './constants'; import { SEARCH_BAR_PLACEHOLDER } from './translations'; import { AlertsSearchBarProps, QueryLanguageType } from './types'; @@ -15,6 +16,8 @@ import { useAlertDataView } from '../../hooks/use_alert_data_view'; import { TriggersAndActionsUiServices } from '../../..'; import { useRuleAADFields } from '../../hooks/use_rule_aad_fields'; +const SA_ALERTS = { type: 'alerts', fields: {} } as SuggestionsAbstraction; + // TODO Share buildEsQuery to be used between AlertsSearchBar and AlertsStateTable component https://github.com/elastic/kibana/issues/144615 export function AlertsSearchBar({ appName, @@ -49,7 +52,7 @@ export function AlertsSearchBar({ } = useRuleAADFields(ruleTypeId); const indexPatterns = - ruleTypeId && aadFields?.length ? [{ title: ruleTypeId, fields: aadFields }] : [dataView!]; + ruleTypeId && aadFields?.length ? [{ title: ruleTypeId, fields: aadFields }] : dataView; const onSearchQuerySubmit = useCallback( ({ dateRange, query: nextQuery }: { dateRange: TimeRange; query?: Query }) => { @@ -102,6 +105,7 @@ export function AlertsSearchBar({ showSubmitButton={showSubmitButton} submitOnBlur={submitOnBlur} onQueryChange={onSearchQueryChange} + suggestionsAbstraction={SA_ALERTS} /> ); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts new file mode 100644 index 0000000000000..3980c174528ed --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts @@ -0,0 +1,55 @@ +/* + * 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 { Spaces } from '../../../scenarios'; +import { getUrlPrefix } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function createRuleSuggestionValuesTests({ getService }: FtrProviderContext) { + const space1 = Spaces[0].id; + + describe('alerts/suggestions/values', async () => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); + }); + + it('Get service.name value suggestion in default space for super user', async () => { + const response = await supertest + .post(`${getUrlPrefix('default')}/internal/alerts/suggestions/values`) + .set('kbn-xsrf', 'foo') + .send({ + field: 'service.name', + filters: [], + query: 'op', + }); + expect(response.body).toEqual(expect.arrayContaining(['opbeans-python', 'opbeans-java'])); + }); + + it('Get service.name value suggestion in space 1 for super user', async () => { + const response = await supertest + .post(`${getUrlPrefix(space1)}/internal/alerts/suggestions/values`) + .set('kbn-xsrf', 'foo') + .send({ + field: 'service.name', + filters: [], + query: 'op', + }); + expect(response.body).toEqual([]); + }); + }); +} diff --git a/x-pack/test/rule_registry/common/lib/authentication/roles.ts b/x-pack/test/rule_registry/common/lib/authentication/roles.ts index 36fe754d3e50a..89878a10cc6d0 100644 --- a/x-pack/test/rule_registry/common/lib/authentication/roles.ts +++ b/x-pack/test/rule_registry/common/lib/authentication/roles.ts @@ -191,6 +191,28 @@ export const securitySolutionOnlyAllSpacesAll: Role = { }, }; +export const securitySolutionOnlyAllSpacesAllWithReadESIndices: Role = { + name: 'sec_only_all_spaces_all_with_read_es_indices', + privileges: { + elasticsearch: { + indices: [ + { + names: ['*'], + privileges: ['all'], + }, + ], + }, + kibana: [ + { + feature: { + siem: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + export const securitySolutionOnlyReadSpacesAll: Role = { name: 'sec_only_read_spaces_all', privileges: { @@ -468,6 +490,7 @@ export const allRoles = [ observabilityOnlyAll, observabilityOnlyRead, securitySolutionOnlyAllSpacesAll, + securitySolutionOnlyAllSpacesAllWithReadESIndices, securitySolutionOnlyReadSpacesAll, observabilityOnlyAllSpacesAll, logsOnlyAllSpacesAll, diff --git a/x-pack/test/rule_registry/common/lib/authentication/users.ts b/x-pack/test/rule_registry/common/lib/authentication/users.ts index be6021307e819..2a63c296d842a 100644 --- a/x-pack/test/rule_registry/common/lib/authentication/users.ts +++ b/x-pack/test/rule_registry/common/lib/authentication/users.ts @@ -29,6 +29,7 @@ import { observabilityOnlyReadSpace2, observabilityMinReadAlertsAllSpacesAll, observabilityOnlyAllSpacesAllWithReadESIndices, + securitySolutionOnlyAllSpacesAllWithReadESIndices, } from './roles'; import { User } from './types'; @@ -157,6 +158,12 @@ export const secOnlyReadSpacesAll: User = { roles: [securitySolutionOnlyReadSpacesAll.name], }; +export const secOnlySpacesAllEsReadAll: User = { + username: 'sec_only_all_spaces_all_with_read_es_indices', + password: 'sec_only_all_spaces_all_with_read_es_indices', + roles: [securitySolutionOnlyAllSpacesAllWithReadESIndices.name], +}; + export const obsOnlySpacesAll: User = { username: 'obs_only_all_spaces_all', password: 'obs_only_all_spaces_all', @@ -279,6 +286,7 @@ export const allUsers = [ noKibanaPrivileges, obsOnlyReadSpacesAll, secOnlySpacesAll, + secOnlySpacesAllEsReadAll, secOnlyReadSpacesAll, obsOnlySpacesAll, logsOnlySpacesAll, diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index e0dd8aedb2766..9216d110a5268 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -8,21 +8,11 @@ import expect from '@kbn/expect'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { RuleRegistrySearchResponse } from '@kbn/rule-registry-plugin/common/search_strategy'; -import { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { - deleteAllAlerts, - createSignalsIndex, - deleteAllRules, - getRuleForSignalTesting, - createRule, - waitForSignalsToBePresent, - waitForRuleSuccess, -} from '../../../../detection_engine_api_integration/utils'; -import { - obsOnlySpacesAllEsRead, obsOnlySpacesAll, logsOnlySpacesAll, + secOnlySpacesAllEsReadAll, } from '../../../common/lib/authentication/users'; type RuleRegistrySearchResponseWithErrors = RuleRegistrySearchResponse & { @@ -30,19 +20,12 @@ type RuleRegistrySearchResponseWithErrors = RuleRegistrySearchResponse & { message: string; }; -const ID = 'BhbXBmkBR346wHgn4PeZ'; - // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const secureBsearch = getService('secureBsearch'); - const log = getService('log'); const kbnClient = getService('kibanaServer'); - const es = getService('es'); - - const SPACE1 = 'space1'; describe('ruleRegistryAlertsSearchStrategy', () => { let kibanaVersion: string; @@ -116,23 +99,14 @@ export default ({ getService }: FtrProviderContext) => { describe('siem', () => { before(async () => { - await createSignalsIndex(supertest, log); - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); - - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id: createdId } = await createRule(supertest, log, rule); - await waitForRuleSuccess({ supertest, log, id: createdId }); - await waitForSignalsToBePresent(supertest, log, 1, [createdId]); + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); }); after(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/alerts/8.1.0' + ); await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); }); @@ -140,8 +114,8 @@ export default ({ getService }: FtrProviderContext) => { const result = await secureBsearch.send({ supertestWithoutAuth, auth: { - username: obsOnlySpacesAllEsRead.username, - password: obsOnlySpacesAllEsRead.password, + username: secOnlySpacesAllEsReadAll.username, + password: secOnlySpacesAllEsReadAll.password, }, referer: 'test', kibanaVersion, @@ -151,7 +125,7 @@ export default ({ getService }: FtrProviderContext) => { }, strategy: 'privateRuleRegistryAlertsSearchStrategy', }); - expect(result.rawResponse.hits.total).to.eql(1); + expect(result.rawResponse.hits.total).to.eql(50); const consumers = result.rawResponse.hits.hits.map( (hit) => hit.fields?.['kibana.alert.rule.consumer'] ); @@ -162,8 +136,8 @@ export default ({ getService }: FtrProviderContext) => { const result = await secureBsearch.send({ supertestWithoutAuth, auth: { - username: obsOnlySpacesAllEsRead.username, - password: obsOnlySpacesAllEsRead.password, + username: secOnlySpacesAllEsReadAll.username, + password: secOnlySpacesAllEsReadAll.password, }, referer: 'test', kibanaVersion, @@ -185,8 +159,8 @@ export default ({ getService }: FtrProviderContext) => { const result = await secureBsearch.send({ supertestWithoutAuth, auth: { - username: obsOnlySpacesAllEsRead.username, - password: obsOnlySpacesAllEsRead.password, + username: secOnlySpacesAllEsReadAll.username, + password: secOnlySpacesAllEsReadAll.password, }, referer: 'test', kibanaVersion, @@ -204,7 +178,7 @@ export default ({ getService }: FtrProviderContext) => { }, strategy: 'privateRuleRegistryAlertsSearchStrategy', }); - expect(result.rawResponse.hits.total).to.eql(1); + expect(result.rawResponse.hits.total).to.eql(50); const runtimeFields = result.rawResponse.hits.hits.map( (hit) => hit.fields?.[runtimeFieldKey] ); @@ -214,10 +188,10 @@ export default ({ getService }: FtrProviderContext) => { describe('apm', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts'); + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts'); + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); }); it('should return alerts from apm rules', async () => { @@ -234,9 +208,9 @@ export default ({ getService }: FtrProviderContext) => { featureIds: [AlertConsumers.APM], }, strategy: 'privateRuleRegistryAlertsSearchStrategy', - space: SPACE1, + space: 'default', }); - expect(result.rawResponse.hits.total).to.eql(2); + expect(result.rawResponse.hits.total).to.eql(9); const consumers = result.rawResponse.hits.hits.map( (hit) => hit.fields?.['kibana.alert.rule.consumer'] ); From 84ca85d0efa30ff0de7922c38bb2ceca875f72f9 Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Mon, 21 Aug 2023 15:16:39 -0700 Subject: [PATCH 24/29] Upgrade EUI to v87.1.0 (#163961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `v86.0.0`⏩`v87.1.0` ⚠️ The biggest set of type changes in this PR come from the breaking change that makes `pageSize` and `pageSizeOptions` now optional props for `EuiBasicTable.pagination`, `EuiInMemoryTable.pagination` and `EuiDataGrid.pagination`. This caused several other components that were cloning EUI's pagination type to start throwing type warnings about `pageSize` being optional. Where I came across these errors, I modified the extended types to require `pageSize`. These types and their usages may end up changing again in any case once the Shared UX team looks into https://github.com/elastic/kibana/issues/56406. --- ## [`87.1.0`](https://github.com/elastic/eui/tree/v87.1.0) - Updated the underlying library powering `EuiAutoSizer`. This primarily affects typing around the `disableHeight` and `disableWidth` props ([#6798](https://github.com/elastic/eui/pull/6798)) - Added new `EuiAutoSize`, `EuiAutoSizeHorizontal`, and `EuiAutoSizeVertical` types to support `EuiAutoSizer`'s now-stricter typing ([#6798](https://github.com/elastic/eui/pull/6798)) - Updated `EuiDatePickerRange` to support `compressed` display ([#7058](https://github.com/elastic/eui/pull/7058)) - Updated `EuiFlyoutBody` with a new `scrollableTabIndex` prop ([#7061](https://github.com/elastic/eui/pull/7061)) - Added a new `panelMinWidth` prop to `EuiInputPopover` ([#7071](https://github.com/elastic/eui/pull/7071)) - Added a new `inputPopoverProps` prop for `EuiRange`s and `EuiDualRange`s with `showInput="inputWithPopover"` set ([#7082](https://github.com/elastic/eui/pull/7082)) **Bug fixes** - Fixed `EuiToolTip` overriding instead of merging its `aria-describedby` tooltip ID with any existing `aria-describedby`s ([#7055](https://github.com/elastic/eui/pull/7055)) - Fixed `EuiSuperDatePicker`'s `compressed` display ([#7058](https://github.com/elastic/eui/pull/7058)) - Fixed `EuiAccordion` to remove tabbable children from sequential keyboard navigation when the accordion is closed ([#7064](https://github.com/elastic/eui/pull/7064)) - Fixed `EuiFlyout`s to accept custom `aria-describedby` IDs ([#7065](https://github.com/elastic/eui/pull/7065)) **Accessibility** - Removed the default `dialog` role and `tabIndex` from push `EuiFlyout`s. Push flyouts, compared to overlay flyouts, require manual accessibility management. ([#7065](https://github.com/elastic/eui/pull/7065)) ## [`87.0.0`](https://github.com/elastic/eui/tree/v87.0.0) - Added beta `componentDefaults` prop to `EuiProvider`, which will allow configuring certain default props globally. This list of components and defaults is still under consideration. ([#6923](https://github.com/elastic/eui/pull/6923)) - `EuiPortal`'s `insert` prop can now be configured globally via `EuiProvider.componentDefaults` ([#6941](https://github.com/elastic/eui/pull/6941)) - `EuiFocusTrap`'s `crossFrame` and `gapMode` props can now be configured globally via `EuiProvider.componentDefaults` ([#6942](https://github.com/elastic/eui/pull/6942)) - `EuiTablePagination`'s `itemsPerPage`, `itemsPerPageOptions`, and `showPerPageOptions` props can now be configured globally via `EuiProvider.componentDefaults` ([#6951](https://github.com/elastic/eui/pull/6951)) - `EuiBasicTable`, `EuiInMemoryTable`, and `EuiDataGrid` now allow `pagination.pageSize` to be undefined. If undefined, `pageSize` defaults to `EuiTablePagination`'s `itemsPerPage` component default. ([#6993](https://github.com/elastic/eui/pull/6993)) - `EuiBasicTable`, `EuiInMemoryTable`, and `EuiDataGrid`'s `pagination.pageSizeOptions` will now fall back to `EuiTablePagination`'s `itemsPerPageOptions` component default. ([#6993](https://github.com/elastic/eui/pull/6993)) - Updated `EuiHeaderLinks`'s `gutterSize` spacings ([#7005](https://github.com/elastic/eui/pull/7005)) - Updated `EuiHeaderAlert`'s stacking styles ([#7005](https://github.com/elastic/eui/pull/7005)) - Added `toolTipProps` to `EuiListGroupItem` that allows customizing item tooltips. ([#7018](https://github.com/elastic/eui/pull/7018)) - Updated `EuiBreadcrumbs` to support breadcrumbs that toggle popovers via `popoverContent` and `popoverProps` ([#7031](https://github.com/elastic/eui/pull/7031)) - Improved the contrast ratio of disabled titles within `EuiSteps` and `EuiStepsHorizontal` to meet WCAG AA guidelines. ([#7032](https://github.com/elastic/eui/pull/7032)) - Updated `EuiSteps` and `EuiStepsHorizontal` to highlight and provide a more clear visual indication of the current step ([#7048](https://github.com/elastic/eui/pull/7048)) **Bug fixes** - Single uses of `` now align right as expected without needing a previous `side="left"` sibling. ([#7005](https://github.com/elastic/eui/pull/7005)) - `EuiPageTemplate` now correctly displays `panelled={true}` ([#7044](https://github.com/elastic/eui/pull/7044)) **Breaking changes** - `EuiTablePagination`'s default `itemsPerPage` is now `10` (was previously `50`). This can be configured through `EuiProvider.componentDefaults`. ([#6993](https://github.com/elastic/eui/pull/6993)) - `EuiTablePagination`'s default `itemsPerPageOptions` is now `[10, 25, 50]` (was previously `[10, 20, 50, 100]`). This can be configured through `EuiProvider.componentDefaults`. ([#6993](https://github.com/elastic/eui/pull/6993)) - Removed `border` prop from `EuiHeaderSectionItem` (unused since Amsterdam theme) ([#7005](https://github.com/elastic/eui/pull/7005)) - Removed `borders` object configuration from `EuiHeader.sections` ([#7005](https://github.com/elastic/eui/pull/7005)) **CSS-in-JS conversions** - Converted `EuiHeaderAlert` to Emotion; Removed unused `.euiHeaderAlert__dismiss` CSS ([#7005](https://github.com/elastic/eui/pull/7005)) - Converted `EuiHeaderSection`, `EuiHeaderSectionItem`, and `EuiHeaderSectionItemButton` to Emotion ([#7005](https://github.com/elastic/eui/pull/7005)) - Converted `EuiHeaderLinks` and `EuiHeaderLink` to Emotion; Removed `$euiHeaderLinksGutterSizes` Sass variables ([#7005](https://github.com/elastic/eui/pull/7005)) - Removed `$euiHeaderBackgroundColor` Sass variable; use `$euiColorEmptyShade` instead ([#7005](https://github.com/elastic/eui/pull/7005)) - Removed `$euiHeaderChildSize` Sass variable; use `$euiSizeXXL` instead ([#7005](https://github.com/elastic/eui/pull/7005)) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Patryk Kopyciński --- .../public/components/page/index.tsx | 2 +- package.json | 2 +- .../header/__snapshots__/header.test.tsx.snap | 34 ++++---- .../src/ui/header/header.tsx | 7 +- .../src/ui/header/header_logo.scss | 2 +- .../src/ui/header/header_nav_controls.tsx | 7 +- .../__snapshots__/i18n_service.test.tsx.snap | 4 + .../src/i18n_eui_mapping.tsx | 14 ++++ .../src/exception_items/index.tsx | 6 +- .../src/types/index.ts | 4 +- src/dev/license_checker/config.ts | 2 +- x-pack/packages/ml/data_grid/lib/types.ts | 2 +- .../components/data_table/index.tsx | 2 +- .../hooks/use_cloud_posture_table/utils.ts | 2 +- .../latest_findings/latest_findings_table.tsx | 2 +- .../resource_findings_table.tsx | 2 +- .../index_data_visualizer_view.tsx | 1 - .../layout/account_header/account_header.tsx | 4 +- .../heatmap_style_editor.test.tsx.snap | 8 +- .../analytics_list/use_table_settings.ts | 2 +- .../nav_control/nav_control_service.test.ts | 4 +- .../components/exceptions_utility/index.tsx | 2 +- .../components/list_exception_items/index.tsx | 2 +- .../hooks/use_list_exception_items/index.ts | 2 +- .../side_panel/side_panel_flex_item.tsx | 2 +- .../nav_control/nav_control_popover.test.tsx | 10 +-- ...ctions_connectors_event_log_list_table.tsx | 4 +- .../event_log/event_log_data_grid.tsx | 2 +- .../event_log/event_log_pagination_status.tsx | 5 +- .../components/rule_error_log.tsx | 2 +- .../components/rule_event_log_list_table.tsx | 4 +- .../apps/aiops/change_point_detection.ts | 4 +- yarn.lock | 80 +++++++++++-------- 33 files changed, 125 insertions(+), 107 deletions(-) diff --git a/examples/bfetch_explorer/public/components/page/index.tsx b/examples/bfetch_explorer/public/components/page/index.tsx index 921f4b5fa1635..6e282f528362b 100644 --- a/examples/bfetch_explorer/public/components/page/index.tsx +++ b/examples/bfetch_explorer/public/components/page/index.tsx @@ -16,7 +16,7 @@ export interface PageProps { export const Page: React.FC = ({ title = 'Untitled', sidebar, children }) => { return ( - + {sidebar} diff --git a/package.json b/package.json index e24f8094b9509..ba07ef6fe5c17 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.9.1-canary.1", "@elastic/ems-client": "8.4.0", - "@elastic/eui": "86.0.0", + "@elastic/eui": "87.1.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap index 319da409fe13c..0f1b092c28d46 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/__snapshots__/header.test.tsx.snap @@ -48,10 +48,10 @@ Array [ data-fixed-header="true" >
@@ -150,17 +150,17 @@ Array [ data-fixed-header="true" >